Project

Concept

When I first conceived Heap (back then there wasn’t a name for it yet), I knew very few things about the outcome that I wanted and how to get there. That’s why, in order to make more real the ideas popping in my head, I started to track down the design principles and the concept I wanted to follow.

To begin, I knew that I wanted to make a stacking game for kids of age around 8-12 about the consequences of waste products. Beside the game mechanisms and the production techniques involved, this meant that I surely had to provide features that could ensure safety of use and be friendly. In fact, from the frist steps I pursued a design that could inspire usability and manipulation, granting robustness, nice tactile sensations and accessibility, all this avoiding displays and other devices that could distract the users from what they were doing: I wanted the players to stay focused on the task they were doing.

This is because another feature that I wanted was to prompt feedbacks after user interaction. Every interaction with the game resulted in a feedback, be it positive or negative, that could portray the progress in the game and grant or limit its continuation.

These guidelines alone were enough to start designing the project nosedive, but I adopted a couple more constraints that could help me to take more precise decision.

First of all, I decided to have a project that could be the most culture-free I could conceive. Having to deal with children, making them to read texts, make calculations or follow too many abstract rules can be a buzzkill so I wanted to opt for something more intuitive and physical, in the sense of behaving more like the physical world. The other principle I’ve chosen to adopt was to upcycle as many material as I could to craft the final prototype of my project. I set this both as a personal challenge and to follow some a coherence in the underlying theme of the game.

Design

Structure

When I started to design the structure for Heap, I thought that I wanted to make a frame that could have a lot of gaps and empty spaces. This is for two reasons: making the user a sort peeping tom for the “under the hood” and give a more light-weight feeling of the project.

Furthermore - always keeping the kids audience of the project in my mind - I thought that the whole size of the game shouldn’t have been more than that of a school desk (figuring out that it is one of the most used - and hence recognizable - “interface” in the life of kid).

So, on OnShape, I designed a structure made out of plain boards kept together with joints that left a gap in every side.

joints-sketch

structure-sketch

All the measurements are made as parametric values, since I had not yet the physical material with me when I started to make the design and I wanted to allow me to adapt the sketches to many configurations.

In this phase I had to resort to some trigonometric formulas - something I hadn’t done in more than a decade and, in full disclosure, made me sweat a lot - and by doing so I granted myself a model that would always be consistent and reliable no matter the values I used for the parameters.

structure-design

A non-trivial task was to design the central part of the structure. In it I would have to accommodate a load bar, a dc motor and load surface that couldn’t be fixed to the surface but still shouldn’t move too much or dislocate. As a starting point, surfing on the web I found the STL models for both the load bar via GrabCad and the dc motor via McMaster that I imported and adapted for my project. In the end I’ve chosen to attach the dc motor at the bottom of the load surface through a custom designed slot. Then I located this part on the top of a custom designed cylinder onto which it would fit loosely (with just few mm of gap) so make it rotate and move freely over the z axis.

weight-base

The dc motor has another peculiar feature. I designed a load to fasten to its shaft and holes in it onto where to locate small pieces of iron bars that, when the motor is activated, make an eccentric movement and make everything vibrate.

weight-base

For the stacking blocks, I’ve drawn several shapes that arranged together could compose a wide variety of shapes. I also extruded them at different heights to figure out how they would appear if composed of different materials.

blocks-sketch

blocks-design.png

System

For the electronics, I decided to use Eagle and divide the most important functions in different boards. I did this because I wanted to show carefully how the system was divided and also because since I needed two different voltages of power supply I preferred to keep certain features separated.

For this reason, the first thing that I designed was a simple breakout board for the power supply. The dc motor I was going to use could handle with no problem voltages up to 12v, but a 9v battery could do the job as well, so I designed a schematic around it.

power-schematic.png

As wrote, this board is nothing more that a circuit breakout for different voltages, so I grouped of different corners connectors for ground, 5V and 9V signals. I used a linear voltage regulator to convert the 9v from the battery to 5v and a 100uF capacitor to stabilize the signal.

Here’s the resulting board.

power-board.png

Please, note that I also provided holes to screw to board to the wooden boards and that I left aesthetical traces that could make understand easily which connectors do what.

The next step was to design the board that would handle all the central operations of the system and coordinate the various devices. In this core board I had to provide links to the dc motor driver, the load bar, i2c, ftdi and isp connectors. Due to the large amount of features and connections, the choice of a AtMega 328 was the most obvious, also because it granted me few more pins that I could use to implement extra features.

Here’s the schematic I came up with.

core-schematic.png

In addition to what already listed, I provided also a power connector, an LED for testing purpose and few pins that in mind I could eventually link to potentiometers in order to adjust system parameters of the run.

Here’s the board layout.

core-board.png

A note of the I2C network connection: when I was planning the general functioning of the project, I realized that I might have had different systems running in parallel (i. e.: sounds, lights, motor, etc.). For the purpose of a more smooth experience of play I thought having different controllers handling this functionalities separately could have done a better job than having a single controller handling everything all by itself. That’s why I implemented an I2C network, so to have the core board to communicate commands and receive feedbacks for its peripheral boards.

The dc motor is controlled directly by the core board, but since it requires a different voltage I opted to design a separate board with the dc driver connected through two PWM to the core board.

driver-schematic.png

In this board is featured the dc driver (which actually is an H bridge controller), stabilizing capacitors, connectors for the two voltages, the motor and the core board.

driver-board.png

Note that the traces used for 9V and ground signals are significantly wider, so to not generate resistance or noise in the board.

To add extra features to the system, I designed a speaker board controlled with an ATtiny 45 that through the I2C network would receive commands from the core board and perform accordingly.

speaker-schematic.png

The speaker is connected to the board and modulated with an N-mosfet, and beyond the I2C and ISP connectors I added just an LED for testing feedbacks.

speaker-board.png

Note that I provided that 9v current to power the speaker but in the end I found more apt to use a buzzer for which 5v are enough, so in this connection I would provide that current.

FSM

I designed a rather open and versatile electronic system because essentially whenever games are involved a lot of testing and adaptation are required from the code side, and that can be seriously limited by a hardware with little foresight. Nonetheless, I designed everything with a loose flowchart already in my mind that later I transposed on I’ve drawn on Draw.io in the form of a Finite-State Machine so to be sure that my game mechanics were loophole-less.

general-flowchart.png

Later I would use this flowchart as guidelines to write the scripts running on my boards.

Graphics

I’m not great graphic designer, and I know that dealing with games (especially for children) requires a good lot of skill in this field. However, I couldn’t leave my project without graphics so I used Gimp to patch few templates to fill the otherwise blank elements of my game.

On a first account, what I needed were the designs for a logo, the stacking cards and some eventual decoration for the structure.

After choosing font and colors playful and friendly enough to patch together a logo and theme identity I started to draw the templates for the cards

gimp-rear

In the design I added also the logos of our lab and Fab Academy and the creative common license logo I’ve chosen for this project.

For the front of the cards I needed several simple images of everyday items that I made out of simple, flat, free logos I found browsing on Google Images. For every logo I made few modifications, essentially to clean edges and remove colors.

icons

Then I juxtaposed them of a coordinated template and obtained my rather simple but effective cards design.

gimp-front

Crafting

Laser cutter

As soon as I received the wood necessary to craft the structure, I downloaded from OnShape the dxf files of the main components and went to laser cut them. The wood I was using was a plywood with 1cm of thickness. It required me a lot of testing before having an acceptable result because laser-cutting panels so thick may end in imprecise cut or scorched wood.

In the end, the cutting setting I used on the Trotec Speedy 400 Flexx we have in our lab were the following:

  • Laser type: CO2
  • Power: 98%
  • Speed: 0.13%

2by4s.png

In this way I obtained a rather clean cut with a kerf value of almost 0.2mm. The wood pieces splintered a bit when I removed them from the board, but sanding them all resulted in a nice and finished result.

sanding.png

On one of the side I also engraved logos of project, lab, Fab Academy and CC.

engraving.png

I also used the laser cutter to craft the stacking blocks, whose DXF was again downloaded from the Onshape models. I made them out of two materials, acrylic cast and plywood, upcycled from by-products and remainders of other works.

acrylic-cutting.png

Furthermore, I used cardboard (saved from the rest of delivery boxes left abandoned in the lab) to make the frame of the playing cards. On the laser cutter, I used the following parameters:

  • Laser type: CO2
  • Power: 80%
  • Speed: 0.5%

To design them, I used the same graphic template of the cards reducing it to the outline and a centered hole.

cardboard-cutting-design

The values I used to cut the cardboard are the following:

cardboard-cutting.png

3D printer

I used an Ultimaker 2+ and an Ultimaker 3 to print all the custom made pieces that I could craft with other techniques. To create the gcode for the printers, I imported the STL models downloaded from Onshape to Cura and adapted many priting values according to my specific needs.

base-print

For making the cylinder for the weighing base and few blocks I used a common black PLA spun with the following printing parameter:

  • Printing Temperature: 205°
  • Build Plate Temperature: 60°
  • Layer height: 0.2mm
  • Wall thickness: 0.7mm
  • Infill Density: 15%
  • Infill Pattern: Cubic Subdivision

block-printing.png

cyinder.png

Note that since the piece in the picture above has a lot of overhanging surface I also printed some support customized so to not have too much time spent of printing useless support. To do so, I adopted the following values:

  • Support placement: Touching Build Plate
  • Support density: 10%
  • Support X/Y Distance: 3mm
  • Minimum Support X/Y Distance: 0.2mm

To print all the other pieces I used an ASA filament called ApolloX. This filament is quite prices and has good mechanical properties, making it really apt for hardware-substitutes like joints.

joint-printing.png

printing-slot.png

These were the printing setting for this filament:

  • Printing Temperature: 250°
  • Build Plate Temperature: 80°
  • Layer height: 0.2mm
  • Wall thickness: 0.7mm
  • Infill Density: 40%
  • Infill Pattern: Quarter Cubic

Electronics

I tried two processes for milling the pcb boards.

At first, I made an attempt with the laser cutter. To make a circuit out of a plain PCB board, I used Adobe Illustrator and generated from the layouts for the power supply board (I’m used to make three files for every board: traces, holes and outline) an SVG file with three layers:

  • one to cut the copper traces
  • one to cut the copper outline
  • one to cut the phenolic paper outline

laser-milling.png

The values used on the laser cutter are the following

Traces

  • Laser type: Fiber
  • Power: 100%
  • Speed: 3.00%
  • Passes: 4

Copper outline

  • Laser type: Fiber
  • Power: 100%
  • Speed: 0.05%
  • Passes: 2

Phenolic paper outline

  • Laser type: CO2
  • Power: 90%
  • Speed: 0.4%

The result was rather good, but the laser cutter was really busy in those days for other projects so I had to resort to typical PCB milling solution.

From Eagle I exported the PNG files of every board and made through [Fab Modules]() the CAM files accepted by our Roland SRM-20. In this way, I made all the other boards for my project.

milled-board.png

With the bills of materials in front of me, I soldered all the components for every board, testing them once the job was done with a multimeter.

soldering.png

boards.png

To complete my system, I ordered a 10kg load bar and a load cell amplifier produced by SparkFun, whose wires I had to crimp with pins to make them pluggable.

crimping.png

Assembling

After crafting all the parts of my project I proceeded in assembling and finishing them.

Using a hot glue gun was the most quick and efficient way to paster together the pieces of acrylic and wood remainders for the stacking blocks.

hot-glue.png

Following a tutorial by Sparkfun I figured out the way to install the load bar. First I marked the position in the center with the required offset.

bar-positioning.png

Then, with the help of bolts and nuts I fastened the various parts in a z shape.

load-bar.png

Assembling the game structure was a rather long task, since it is made by a great number of pieces. First of all I marked with a pencil all the spots onto which would fit a joint screwhole.

marking-woods.png

Then with a tower drill I punched holes in those spots to facilitate the passage of bolts.

tower-drill.png

Once all the holes were drilled, I started to fasten all the joints to their related position.

assembling.png

To make the weighing base I had to fix a little issue. I might have miscalculated the offset of the payload of the dc motor, since it scratched and gripped on the surface where the motor was fastened. This cause the motor to friction and not turn properly, so I sawed a bit of the last piece of the base and the problem was solved.

dc-motor-assembling.png

Note, that all the pieces of this base are kept together with hot glue (I wanted not to have screws facing the user over this base. I deemed them not fancy for this purpose and an obstacle for a balanced stacking).

With the motor ready, I wanted to see how all my electronic component would look like when completely plugged, but the result was a messy tangle-jungle.

tangle-jungle.png

Since working with this setup was all but practical, I decided to locate the various boards over their specific wooden board and screw them steady.

boards-topview.png

I also added short wires to every connector, so to have easy and quick access to the pin of every board.

After mounting the boards, I connected all the boards according their joints and won’t hide that I was excited and surprised to see that everything was fitting just as designed.

assembled.png

To craft the playing cards, I prepared a PDF with all the cards in real size inside in a single A4 sheet, both for the front and rear, and printed it in full color over 250gm/m2 cardboard

cards-print-file.png

card-printing.png

I adapted the same files to be used in the laser cutter, by erasing everything but the hairtight outlines and saving them as SVG.

cards-cutting

I put the printed sheets inside the laser cutter, aligned the laser at their center and launched the cutting file with the following parameters:

  • Laser type: CO2
  • Power: 60%
  • Speed: 2.50%

In this way, I had in my hand in a matter of few minutes all the printed cards. After that, I took the cardboard frames cut before and a tube of vynil glue, and started to paste the rear of the cards on a side of the cardboards.

glueing-cards.png

card-rear.png

As soon as the glue dried, I filled the cardboard frames with all the kinds of material I could find: wood remainders, spare screws, broken bearings, etc. What I wanted to obtain was a lot of cards of different weights.

payloading.png

While making them I used a kitchen scale to compare the weights and be sure that there at least a couple grams of difference among every card.

Once the loads were defined, I pasted the other side of the card to close the frames and left them to dry.

cards.png

Testing

Once everything was mounted, I figured that testing all the various components before going one could have saved some troubles.

To begin I started to program both core and speaker boards. Having installed an LED, I simply scripted a function to turn it on to see if the controller was responding. In both boards, it did.

testing-board.png

After that, I wanted to test if load bar and load cell amplifier functioned as required. Sparkfun provides an useful script used to both test and calibrate the bar. It relies on a free library by GitHub user Bogde that is specifically written for HX711 integrated circuit, such as the one on my load cell amplifier.

I plugged an Arduino Uno with the aforementioned code flashed on and tested the bar with a load of known weight (a concrete block made during Molding and Casting week’s assignment).

bar-calibration.png

This script allows you to figure out which is the best calibration factor for your setting and in my case it resulted to be 210000.

Once aware of this value, I proceeded to evaluate the weight of every playing card and every stacking block which I will use later to complete my code.

evaluating-cards.png

evaluating-loads.png

Later on, I proceeded to check the functioning of every system in my project by writing test scripts on Arduino IDE for every system and flashing them on the controllers.

The following is a script to test the dc motor:

//pins to control motor driver
#define dc1 5
#define dc2 6

void setup() {
	pinMode(dc1, OUTPUT);
	pinMode(dc2, OUTPUT);

//setting one pin to zero allows to drive the motor at the output value of the other pin. If values are reversed, so is the direction of the motor.
	analogWrite(dc2, 0);
}

void loop() {
	for (int i = 1; i < 5; i++) {
//increase motor speed by a fifth every half second
    		analogWrite(dc1, 50 * i);
		delay(500);
	}
}

This is result:

motor-vimeo.png

The following script is to test the buzzer connected to the speaker board:

#define speaker 1
#define highTone 255

void setup() {
	pinMode(speaker, OUTPUT);
}

void loop() {
  	for (short i = 1; i < 5; i++) {
// divide the tone by a fifth every half second
 		analogWrite(speaker, highTone / i);
  		delay(500);
 	}
}

Here’s the result:

speaker-vimeo.png

The following couple of scripts are to test the network functionalities. The master sends a command to the slave, which replies back and the master reacts:

//I2C Master Test
#include "Wire.h"
#define led 8

int i = 1;

//function to blink the LED n times 
void Blink(int n) {
	for (int k = 0; k < n; k++) {
    		digitalWrite(led, HIGH);
		delay(400);
		digitalWrite(led, LOW);
		delay(400);
	}
}

void setup() {
	pinMode(led, OUTPUT);
	Wire.begin(); //master
}

void loop() {
	//Set blink duration
	Wire.beginTransmission(1);
	Wire.write(i);
	Wire.endTransmission();

	//Ask for blink and blink in response
	Wire.requestFrom(1,1); //1 byte from 1
	while (Wire.available()) {
		int c = Wire.read();
		if (c = 1)
			Blink(i);
	}

// blink reset
	if (i > 5)
		i = 1;
	else
		i++;
}
//I2C Slave Test
//clock refactored x12 by I2C library

#include <TinyWireS.h>
#define led 4

int t = 1;

void Blink(int n) {
	for (int k = 0; k < n; k++) {
		digitalWrite(led, HIGH);
		delay(400); 
		digitalWrite(led, LOW);
		delay(400);
	}
}

void DoBlink() {
	Blink(t);
	TinyWireS.write(1); //sends a byte to master
}
    
void SetBlink(int x) {
	while (1 < TinyWireS.available()) {
    		t = TinyWireS.read();
    	}
}

void setup() {
	pinMode(led, OUTPUT);
	TinyWireS.begin(1); //slave address 1
	TinyWireS.onRequest(Blink);
	TinyWireS.onReceive(SetBlink);
}

void loop() {
//does nothing
}

Here’s the result:

network-vimeo.png

In the following code, I test the functioning of the load bar and blink the onboard LED of the core controller if more than half kg is detected:

#include "HX711.h"
#define led 8
//HX711 pins
#define DOUT  2
#define CLK  3

HX711 scale(DOUT, CLK);
float calibration_factor = 210000;
int loadAssessment = 50;
float sensorThreshold = 0.50;

void RapidBlink() {
	digitalWrite(led, LOW);
	for (short i = 0; i < 4; i++) {
		digitalWrite(led, !digitalRead(led));
		delay(50);
	}
}

bool CheckLoad(float value) {
	delay(loadAssessment); //load assessment delay
	float load = scale.get_units(3); //values up to the third decimal
	if (load > value)
		return true;
	return false;
}

void setup() {
	pinMode(led, OUTPUT);

//Scale zeroing and reset routine
	scale.set_scale();
	scale.tare();
	long zero_factor = scale.read_average();
	scale.set_scale(calibration_factor);
}

void loop() {
	if(CheckForLoad(sensorThreshold)) {
		RapidBlink();
	}
}

Here’s the result:

load-bar-vimeo.png

In the end, I crammed into a single sketch several functions to see in they were working finely together.

#include "HX711.h"

#define dc1 5
#define dc2 6
#define led 8

int loadAssessment = 50;

HX711 scale(DOUT, CLK);
float calibration_factor = 210000;

void RapidBlink() {
	digitalWrite(led, LOW);
	for (short i = 0; i < 4; i++) {
		digitalWrite(led, !digitalRead(led));
		delay(50);
	}
}

void Shake(int t) {
	analogWrite(dc1, 255);
	delay(t);
	analogWrite(dc1, 0);
}

bool CheckLoad(float value) {
	delay(loadAssessment); //load assessment delay
	float load = scale.get_units(10);
	if (load > value)
		return true;
	return false;
}

void setup() {
	pinMode(dc1, OUTPUT);
	pinMode(dc2, OUTPUT);
	pinMode(led, OUTPUT);
	analogWrite(dc2, 0); //setting motor direction

//scale reset routine
	scale.set_scale();
	scale.tare();
	long zero_factor = scale.read_average();
	scale.set_scale(calibration_factor);
}

void loop() {
//blinks and shakes if load is more than half kg
	if (CheckLoad(0.5)) {
		RapidBlink();
		Shake(500);
	}
}

Here’s the result:

load-shake-vimeo.png

Seeing this scripts succeed helped me understand better some of the mechanics that I wanted to achieve, furthermore I’d use later some of the already written functions for the final code.

Code

Having two boards, I divided the code in two scripts. The following is for the speaker board. It simply states the parameters for different kind of buzzes and establish the I2C connection. Whenever the board is called by the master, it replies with the requested buzz.

//clock refactored x12 by I2C library
#include <TinyWireS.h>

#define led 4
#define speaker 1
#define highTone 255
#define midHighTone 189
#define midTone 125
#define midLowTone 65
#define midLowLowTone 33
#define lowTone 1

#define lapse 5000
#define shortLapse 10

int t = 1;

void RapidBlink() {
  digitalWrite(led, LOW);
  for (short i = 0; i < 4; i++) {
    digitalWrite(led, !digitalRead(led));
    delay(shortLapse);
  }
}

void Blink(int n) {
  for (int k = 0; k < n; k++) {
    digitalWrite(led, HIGH);
    delay(lapse); 
    digitalWrite(led, LOW);
    delay(lapse);
  }
}

void RapidBuzz() {
  analogWrite(speaker, lowTone);
  delay(shortLapse * 5);
  analogWrite(speaker, 0);
  delay(shortLapse);
  analogWrite(speaker, lowTone);
  delay(shortLapse * 5);
  analogWrite(speaker, 0);
}

void Alarm() {
  TinyWireS.write(1); //sends a byte to master
  switch (t) {
    case 1: //normal
      Buzz();
    break;
    case 2: //urgent
      UrgentBuzz();
    break;
    case 3: //fail
      FailBuzz();
    break;
    case 4: //error
      ErrorBuzz();
    break;
    case 5: //restart
      RestartBuzz();
    break;
    default:
    break;
    }
  Blink(1);
}
    
void SetAlarm(int x) {
  while (1 < TinyWireS.available()) {
    t = TinyWireS.read();
  }
}

void setup() {
  pinMode(led, OUTPUT);
  pinMode(speaker, OUTPUT);
  RapidBlink();
  RapidBuzz();
  
  TinyWireS.begin(1); //slave address 1
  TinyWireS.onRequest(Alarm);
  TinyWireS.onReceive(SetAlarm);
}

void FailBuzz() {
  analogWrite(speaker, midLowTone);
  delay(lapse * 2);
  analogWrite(speaker, 0);
  analogWrite(speaker, lowTone);
  delay(lapse * 4);
  analogWrite(speaker, 0);
  }

void Buzz() {
  analogWrite(speaker, lowTone);
  delay(lapse);
  analogWrite(speaker, 0);
  delay(lapse);
}

void ErrorBuzz() {
  for (int i = 0; i < 3; i++) {
  analogWrite(speaker, lowTone);
  delay(lapse/3);
  analogWrite(speaker, 0);
  delay(lapse/4);
  }
}

void UrgentBuzz() {
  analogWrite(speaker, midLowTone);
  delay(lapse/2);
  analogWrite(speaker, 0);
  delay(lapse/2);
  }

void RestartBuzz() {
  analogWrite(speaker, lowTone);
  delay(lapse/2);
  analogWrite(speaker, 0);
  analogWrite(speaker, midTone);
  delay(lapse/2);
  analogWrite(speaker, 0);
  analogWrite(speaker, midHighTone);
  delay(lapse/2);
  analogWrite(speaker, 0);
  }
  
void loop() {
  //does nothing
}

Then I prepared the code for the Core board. It’s largely based on the scripts used for the testing the various components and the flowchart presented at the beginning of the documentation, but it was also adapted in order to answer to certain specific game mechanics and technical requirements. The code is pretty much self-explanatory and comments are given in order to understand the most articulated parts.

/*
1 - Buzz()
2 - UrgentBuzz()
3 - FailBuzz()
4 - ErrorBuzz()
5 - RestartBuzz()
*/

#include "HX711.h"
#include "Wire.h"

//#define POTENTIOMETERS //for future implementations

//moto pins
#define dc1 5
#define dc2 6

#define led 8

//load bar pins
#define DOUT  2
#define CLK  3

#ifdef POTENTIOMETERS
  #define pot1 A0
  #define pot2 A1
  #define pot3 A2
#endif

#define cards 10

//pre-weighted values of cards and associated blocks
const int cardWeights[cards][2] = {
  {0, 0}, //placeholder
  {14, 23}, //Ball
  {12, 16}, //Block notes & Pen
  {20, 36}, //Delivery box
  {22, 3}, //Shoes
  {32, 34}, //TV
  {6, 14}, //Bottle
  {23, 26}, //Drawing
  {9, 10}, //Book
  {17, 12} //Umbrella
};

//FSM state variable
enum State {start, normal, error, loading, lost};
State state;

float actualLoad;
float wrongLoad;
float tolerance = 1.0; //1g of load tolerance
int loadAssessment = 50; //50 ms of assessment time
int cardDetected;

HX711 scale(DOUT, CLK);
float calibration_factor = 210000;
bool errorFlag;

void RapidBlink() {
  digitalWrite(led, LOW);
  for (short i = 0; i < 4; i++) {
    digitalWrite(led, !digitalRead(led));
    delay(50);
  }
}

//Led blinks n times
void Blink(int n) {
  for (int k = 0; k < n; k++) {
    digitalWrite(led, HIGH);
    delay(400);
    digitalWrite(led, LOW);
    delay(400);
  }
}

//Motor shakes t milliseconds
void Shake(int t) {
  analogWrite(dc1, 255);
  delay(t);
  analogWrite(dc1, 0);
}

//Set alarm in speaker board
void SetAlarm(int n) {
 Wire.beginTransmission(1);
 Wire.write(n);
 Wire.endTransmission();
 RapidBlink();
}

//Request alarm from speaker board
void Alarm() {
  Wire.requestFrom(1,2); //2 byte from device 1
  while (Wire.available()) {
    int c = Wire.read();
    RapidBlink(); //positive feedback
    }
}

//Alarm remote control
void FireAlarm(int a) {
  SetAlarm(a);
  Alarm();
}

//Compares actual load against given value
bool CheckLoad(int value) {
  delay(loadAssessment); //load assessment delay
  float load = scale.get_units(10);
  if ((load > value - tolerance) && (load < value + tolerance)) {
    return true;
  }
  return false;
}

//Checks if load has varies
bool CheckLoadVariation(){
  float tmp = scale.get_units(10);
  if ((tmp > actualLoad  + tolerance) || (tmp < actualLoad - tolerance))
    return true;
  return false;
}

//Checks if a card has been loaded. Returns 0 if no card is matched
int CheckForCard() {
  for (short i = 0; i < cards; i++) {
   if (CheckLoad(cardWeights[i])) {
    return i; 
   }
  }
  return 0;
}

//Checks if load has diminished
bool LoadLoss() {
  if (CheckLoadVariation()) {
    if (scale.get_units() < actualLoad) {
      return true;
    }
  }
  return false;
}

//Evaluate the time to activate the motor in milliseconds between timeMin and timeMax according load and pre-set value mismatch
int EvaluateShake() {
  int timeMax = 3000;
  int timeMin = 1000;
  float t;
  t = (wrongLoad / cardWeights[cardDetected][1]);
  if (t < 1) {
    t = 1 - t;
  } else {
    t = t - 1;
  }
  t = timeMin + (timeMax - timeMin) * t;
  return round(t);
}

//FSM Start state
void Start() {
  FireAlarm(5);
  delay(1000);
  actualLoad = scale.get_units(10);
  cardDetected = 0;
}

//FSM Normal state
void Normal() {
  FireAlarm(1);
  if (CheckLoadVariation()) {
    int tmp = CheckForCard();
    if (tmp != 0) {
      cardDetected = tmp;
      state = loading;
    } else {
      state = error;
    }
  }
}

//FSM Loading state
void Loading() {
  long countDown = 8000;
  long startTime = millis();
  float load;
  bool failFlag = true;
  long 

//Players have countDown time to load the base
  while (t < startTime + countDown) {
    t = millis();
    if (LoadLoss()) {
      failFlag = true;
      break;
    }
    if (startTime + countDown - 3000 < t) {
      FireAlarm(2);
    }
  }

//Checks if there was a load loss, if the weight loaded is wrong or if everything is fine
  if (failFlag) {
    state = lost;  
  } else {
    if (CheckLoad(actualLoad + cardWeights[cardDetected][0] + cardWeights[cardDetected][1])) {
      actualLoad = scale.get_units(10);
      cardDetected = 0;
      state = normal;
    } else {
      wrongLoad =  scale.get_units() - actualLoad;
      errorFlag = true;
      state = error;
    } 
  }
}

//FSM Error state
void Error(bool type) { //type: false generic error, true loading error
  int t;
  FireAlarm(4);
  if (type) {
    t = EvaluateShake();
  } else {
    t = 250;
  }
  Shake(t);
  if (LoadLoss()) {
    state = lost;
  } else {
    state = normal;
    actualLoad = scale.get_units(10);
  }
}

//FSM Lost state
void Lost() {
// loop until base is unloaded
  while (actualLoad - tolerance > 0) {
    actualLoad = scale.get_units();
    FireAlarm(3);
    Shake(3000);
    delay(2000);
  }
  state = start; //FSM begins from scratch
}

void setup() {
  pinMode(dc1, OUTPUT);
  pinMode(dc2, OUTPUT);
  #ifdef POTENTIOMETERS
    pinMode(pot1, INPUT);
    pinMode(pot2, INPUT);
    pinMode(pot3, INPUT);
  #endif
  pinMode(led, OUTPUT);
  RapidBlink(); //led test
  Wire.begin(); // master
  analogWrite(dc2, 0); //setting motor direction

//scale reset routine
  scale.set_scale();
  scale.tare(); //Reset the scale to 0
  scale.set_scale(calibration_factor);
  
  errorFlag = false;
  state = start;
}

void loop() {
  switch (state) {
    case start:
      Start();
      break;
    case normal:
      Normal();
      break;
    case error:
      Error(errorFlag);
      errorFlag = false;
      break;
    case loading:
      Loading();
      break;
    case lost:
      Lost();
      break;
    default:
      break;
  }
}

At the beginning of the code I’ve commented a #define POTENTIOMETERS command which is referred in other parts of the code. This is because in my ideas I want to setup few potentiometers for various purposes, like adjusting the shake intensity or the turn time, but haven’t implemented them yet. Leaving it there, however, makes it clear that the code can be updated and upgraded with ease.

To craft this project I used a total of :

  • 2 * Plywood boards 1200 * 1000 * 10 mm
  • 52 * M4 nuts and bolts of various lengths
  • 15 ~ 20 m of 3D printer filament
  • PCB boards out of various remainders for no more than 32 square cm of material
  • 1 * dc motor
  • 1 * 10kg load bar
  • 1 * load cell amplifier
  • Byproducts from wood and acrylic cast and unsorted hardware for less than 12 kg of weight
  • Cardboard from a delivery box
  • 2 * sheets of 250mg/square m cardboard paper
  • few meters of wire and few dozens of crimpable pins
  • 1 * 9v battery
  • Various electronic components listed in the following B.O.M

I had to waste some material before finding the proper fabrication settings, especially for the wood pieces, and I think that with some rationalization it’s possible to save consistently on 3D filament and hardware. Beside that, most of the materials I’ve used were found laying around the lab or saved from scraps and to my own estimate I’ve spent no more than 30,00€ for buying stuff.

The following link takes to the OnShape parametric model of the project:

The following links are downloads for the files used in this documentation to replicate the project:

Beyond the purpose of this documentation, in the future I’ll keep up a GitHub repository where I’ll update the project along with its various parts and be open to feedbacks and contributions. Here’s the link: