Introduction: The Weight Plate Controller

A controller that acts as a scale together with two buttons that act as a confirm and reset button in-game. While coming up for ideas for what kind of arduino project I wanted to do I settled on combining the game with a physical activity to make it more engaging.


I learned a lot about how load cells work and how to make them accurately calculate weight. It was also fun working with buttons and coming up with the finalized design for the controller.


It was build using components I ordered online and left over shoeboxes I found.

Supplies

The items needed are a computer and these things;

Software:

  • Unity
  • Arduino IDE

Tools:

  • Soldering iron
  • Saw
  • Sanding paper
  • Spray paint
  • Power Drill
  • Multimeter (optional)

Components:

  • Buttons
  • Wires
  • Load Cell (10kg in my case)
  • Load Cell Amplifier - HX711
  • USB B Cable
  • Arduino Uno

Parts:

  • Shoebox
  • Wood
  • Bolts
  • Pin headers

Step 1: Soldering Buttons

First I soldered some pin headers to two buttons so I could attach them easily to the Arduino using jumper wires.


I had to check first with a Multimeter to see which pins of the button made contact when it is being pressed so I could properly solder the pin headers and attach the wires. (I recommend using simpler buttons, but I liked the satisfying feel of these ones.)


After attaching them to the Arduino I moved on to the load cell.

Step 2: Prepping the Load Cell

For the load cell I used a HX711 load cell amplifier as adviced on the store that I bought the load cell from.


The load cell worked different from what I expected. It works more as a balancing device in order to calculate weight than something like calculating it from pressure. (This becomes important later on!)


I soldered the wires from the Load cell directly to the Amplifier and attached pin headers on the other side to also be able to easily attach jumper wires from the Amplifier to the Arduino.


For some in-depth information I recommend checking out this instructable on the HX711 load cell amplifier: https://www.instructables.com/How-to-Interface-HX711-Balance-Module-With-Load-Ce/

Step 3: Setting Up Buttons in Arduino IDE

Now everything was set up and ready to be tested. I downloaded the Arduino IDE and attached the USB B cable to my computer.


To test the buttons I used a library made by a friend that was easy to use and helped a lot with sending the data to Unity.


To use this library just open a sketch in the Arduino IDE and drag and drop the library file attached below.


Library code:

//From Aeralius!


bool signalPins[14] = {};  
bool signalPinsUps[14] = {};
bool signalPinsDowns[14] = {};


void registerSignalCommand( int Pin )
{
  signalPins[Pin]=true; //Add the pin to the pin list thing :)
}

void checkSignals()
{
  for(int i=0;i<14;i++)
  {
    if(signalPins[i]==true)
    {
      //DOWN
      if(!digitalRead(i) and !signalPinsUps[i])
      {
        signalPinsUps[i] = true;
        signalPinsDowns[i] = false;
        Serial.print("D");Serial.print(i); Serial.println(",1");  
      }
      //UP
      if(digitalRead(i) and !signalPinsDowns[i])
      {
        signalPinsUps[i] = false;
        signalPinsDowns[i] = true;
        Serial.print("D");Serial.print(i); Serial.println(",0");
      }
    }
  }
}


Arduino IDE sketch code:

#include "signalenzo.h"

void setup()
{
   //Opening connection
   Serial.begin(115200);

  //Registering all the buttons
  pinMode(8, INPUT_PULLUP);
  registerSignalCommand(8);

  pinMode(7, INPUT_PULLUP);
  registerSignalCommand(7);
}

void loop()
{
  checkSignals();
}


This will be enough to check if the buttons are pressed and return a 1 if they are pressed and a 0 if they are not. (Used for creating a boolean in Unity later!)


It's important to note that you NEED to do pinMode(_, INPUT_PULLUP) instead of output.

The reason why is that the Arduino puts resistance on top of where the electricity is going through so when you push the button the electricty goes to ground which is detected by the digital pin.

Step 4: Extra Prepping for the Load Cell

Before testing the load cell I thought it would be helpful (since large objects should be able to be weighted on the load cell) to attach a surface/support above it.


The load cell usally has a sticker showing which side should be down so it's important to check first before doing this.


I sawed a wooden plank in half and drilled two holes in them to attach them with bolts to the load cell.

This made for a helpful surface to place objects on and didn't impact the weight calculation at all since this can be accounted for during the calibration process.

Step 5: Calibrating Scale in Arduin IDE

Like I said earlier the load cell worked quite differently from what I thought and proved slighlty more difficult than I thought. The main issue is that the load cell doesn't know what 0 is and needs to be calibrated in order to weigh anything accurately on it.


Luckily calibrating the load cell is fairly simple, but will require another library that can be installed in the Arduino IDE.


For this go to Sketch > Include Library > Manage Libraries...

And from here type in: HX711

The first option should be the correct library to use.

The example sketch for the calibration can be found on this Github page: https://github.com/RobTillaart/HX711/tree/master/examples/HX_calibration


(I also recommend setting the baudrate in your sketch to a higher value since the load cell will give high values that otherwise can't be displayed in the serial monitor!)


Arduino IDE code (post calibration):

#include "signalenzo.h"
#include "HX711.h"


#define PIN_SCALE_DT 4
#define PIN_SCALE_SCK 5


float lastValue;


HX711 myScale;


void setup()
{
  myScale.begin(PIN_SCALE_DT, PIN_SCALE_SCK);
  myScale.set_offset(102692);
  myScale.set_scale(429.794738);

   //Opening connection
   Serial.begin(115200);


  //Registering all the buttons  
  pinMode(8, INPUT_PULLUP);
  registerSignalCommand(8);

  pinMode(7, INPUT_PULLUP);
  registerSignalCommand(7);  


  pinMode(8, INPUT_PULLUP);
  registerSignalCommand(4);

  pinMode(7, INPUT_PULLUP);
  registerSignalCommand(2);  
}


void calibrate()
{
  Serial.println("\n\nCALIBRATION\n===========");
  Serial.println("remove all weight from the loadcell");
  //  flush Serial input
  while (Serial.available()) Serial.read();


  Serial.println("and press enter\n");
  while (Serial.available() == 0);


  Serial.println("Determine zero weight offset");
  myScale.tare(20);  // average 20 measurements.
  uint32_t offset = myScale.get_offset();


  Serial.print("OFFSET: ");
  Serial.println(offset);
  Serial.println();

  Serial.println("place a weight on the loadcell");
  //  flush Serial input
  while (Serial.available()) Serial.read();


  Serial.println("enter the weight in (whole) grams and press enter");
  uint32_t weight = 0;
  while (Serial.peek() != '\n')
  {
    if (Serial.available())
    {
      char ch = Serial.read();
      if (isdigit(ch))
      {
        weight *= 10;
        weight = weight + (ch - '0');
      }
    }
  }
  Serial.print("WEIGHT: ");
  Serial.println(weight);
  myScale.calibrate_scale(weight, 20);
  float scale = myScale.get_scale();


  Serial.print("SCALE:  ");
  Serial.println(scale, 6);


  Serial.print("\nuse scale.set_offset(");
  Serial.print(offset);
  Serial.print("); and scale.set_scale(");
  Serial.print(scale, 6);
  Serial.print(");\n");
  Serial.println("in the setup of your project");


  Serial.println("\n\n");
}

void loop()
{
  // if (digitalRead(8)) {
  //   scale.tare(10);
  // }
  //Don't torture the Arduino :)
  delay(50);
  float scaleValue = myScale.get_units(1);


  Serial.print("SCALE,"); Serial.println(scaleValue);
  checkSignals();
  // calibrate();
}


After placing a watercan with exactly 1 liter on it, I calibrated it and tested the accuracy.


In the first video above you can see that when no pressure is applied the value being returned is close to 0.

During this period the scale was really accurate and usually within a 3 gram error margin.


The second video shown above was taken slightly later and shows that the scale has started to gain a slight offset from it's original calibration, altough the difference was small the error margin was now usually around 5-7 grams.


For maximum accuracy I recommend recalibrating the load cell after every modification made to it since the slightest change can cause its returned value to change.

Step 6: Using Arduino Data in Unity

Now that we have the proper data being send by the arduino (as seen in the serial monitor of the Arduino IDE), we can start reading it in back Unity.


Make sure the framework in your Unity project is set to the .NET Framewrok

This option is in Project Settings > Player > Other Settings > Api Compatibility Level


This is so we can use the System.IO.Ports namespace which allows us to read back the serial data from the Arduino.


Unity code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;


public class Arduino : MonoBehaviour
{
    private SerialPort serial;
    [SerializeField] private int baudrate = 115200;
    private const string buttonA = "D8";
    [HideInInspector] public bool buttonAState = false;
    private const string buttonB = "D7";
    [HideInInspector] public bool buttonBState = false;
    private const string weight = "SCALE";
    [HideInInspector] public float weightState = 0;


    private void Awake()
    {
        InitializeArduino();
    }


    private void Start()
    {
        StartCoroutine(GetDataFromSerial());
    }


    private void Update()
    {
        // Button for resetting game
        if (buttonBState)
        {
            Debug.Log("D7 was pressed");
        }
        // Button for ending game
        if (buttonAState)
        {
            Debug.Log("D8 was pressed");
        }
// Current load cell weight
Debug.Log(weightState);
    }


    private void InitializeArduino()
    {
        Time.timeScale = 0;


        foreach (string portName in SerialPort.GetPortNames())
        {
            // Try to open the serial port
            try
            {
                serial = new SerialPort(portName, baudrate, Parity.None, 8, StopBits.One);
                serial.DtrEnable = true;
                serial.DataReceived += new SerialDataReceivedEventHandler(OnSerialDataReceived);
                serial.Open();


                arduinoTextMesh.text = "Arduino connected!";
                Debug.Log($"Arduaaano found on port {portName}");
            }
            catch
            {
                // idk lmao, doesn't matter
            }
        }


        // We should probably check if the serialPort was opened at all, otherwise show some error the controller isn't connected
        Time.timeScale = 1;
    }


    private IEnumerator GetDataFromSerial()
    {
        while (true)
        {
            // Try to read serial
            try
            {
                if (serial.BytesToRead > 0)
                {
                    string input = serial.ReadLine();
                    Debug.Log(input);
                    string[] splitInput = input.Split(",");


                    // Ignore invalid inputs
                    if (splitInput.Length == 2)
                    {
                        switch (splitInput[0])
                        {
                            case buttonA:
                                buttonAState = splitInput[1] == "1";
                                break;
                            case buttonB:
                                buttonBState = splitInput[1] == "1";
                                break;
                            case weight:
                                weightState = float.Parse(splitInput[1]);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            catch
            {
                // Do something if controller isn't found
                Debug.Log("Arduino not found, pls fix");
                InitializeArduino();
            }


            yield return new WaitForSecondsRealtime(0.05f);
        }
    }


    private void OnDestroy()
    {
        serial.Close();
    }
}


Just place this script on an empty gameObject and run the game to see the values appear in the Unity console.

If you set everything up like I did it should work right away otherwise you might need to tweak some values/numbers.


You can do a lot of fun things with the data and if you are interested in more you can check out the Github repository for my Unity project using the Arduino: https://github.com/miyulake/ITTT


Or you can play the game I made for it here: https://miyulake.itch.io/weight-plate

Step 7: Creating the Controller Case

To create the case you can use a simple shoebox like I did, but it can cause some issues.


I decided to keep the load cell grounded by cutting the shoebox in half and cutting a hole through the top where the upper plank could go through.


To hide the bolts a bit better I spray painted the upper plank a metallic silver.

I also spray painted the shoebox black for a nice contrast with the red buttons.

Step 8: Finalizing Controller Case

The main issue is the location of the load cell since it's not allowed to hang off of a surface or it's weight will go into the minus.


So I raised the arduino and load cell a tiny bit by inserting some cardboard into the shoebox first.


I cut some extra holes on the right side for the buttons to go through and tucked the arduino into the corner of the shoebox.


Conveniently the shoebox already had a hole on the side for the USB B cable to go through.

Step 9: Playing the Game

Everything worked out well and I tested the buttons and weight plate (in the above videos) to see if everything was working correctly.


The game responded accurately and fast to the load cell and buttons and after putting the load cell into the controller case it had an offset of about -3 to -5 grams. So still quite accurate and a minor difference from before.

Step 10: Final Touches and Retrospective

Some final touches were the labels to give the controller a refined look.

It was also important to label the uses of the buttons to prevent confusion between the two.


Creating the controller taught me a lot about load cells and how they work.

My main challenge was figuring out how to attach the amplifier and calibrating the load cell properly, but it was definitely worth it for how accurately it calculates weight.


I already had some experience in sending serial port data to Unity which really helped me out here.


A fun assignment and something I might like to revisit someday.