Final Project


Video presentation

Introduction

Honestly, when I began the Fab Academy, I had no idea what I could create for my final project, something truly useful. Despite being busy with academic commitments, I conducted extensive research. Eventually, I settled on the idea of developing a vacuum cleaner for cleaning streets. However, I soon realized that it was too complex of a project. Therefore, I opted to change my focus to designing a prototype version of a dual-axis solar tracker for educational purposes. Unfortunately, due to limitations in equipment and components, I was unable to complete that project.

Ultimately, I decided to stay on the scoreboard idea, my first project in our lab, and I will enhance it further.

Summarizing

Initially, the project aimed to display numbers corresponding to the number of players during substitutions in a game, as well as showing the remaining time. However, the concept or goal has evolved to focus on displaying the score first and then displaying the player substitution numbers, as previously mentioned.

The video below show what was about. You can see when the time is display and then when the substitution is display.

Hence we have completely revisited the project from the source code to the frame of the device we made.

the new goal is to make an authentic scoreboard for basket ball and volley ball and in more we want to design an external module of sensor to create an automatic scoring for basket ball gaming.

Below is a video showing how the device works with a smartphone. As you can see the bluetooth communication works well for wireless control

Our scoreboard is based on 2 microcontrollers one control the P10 module and manage bluetooth communication and the other control LCD screen comunicate with the main MCU and with the external sensor module.

The communication beteween scoreboard and the external sensor module is established using an ESP01

The process of the project is as follow :

The full part of the electronics board is not complete yet but all files are available. the board incorporate ATMEGA328 P chip. or arduino nano.

*Programing

#include <SPI.h>
#include <DMD.h>
#include <TimerOne.h>
#include <Arial14.h>
#include <Arial_black_16.h>
#include <SystemFont5x7.h>
#include "Font_6x14.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>

//Fire up the DMD library as dmd
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1

// set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27,16,2);  

// les variables pour la programmation de l'afficheur
int b1 = 2, b2 = 3, b3 = 4, b4 = 5;
int b4_pressed = false ;
bool bascul1 = true;
bool bascul2 = true;
bool bascul3 = 0;
bool longPressActive = false ;
unsigned long buttonTimer = 0;
unsigned long buttonPressDuration = 0 ;
const unsigned long long_press_time = 1000; //time to consider a long press button
unsigned long prevmillis = 0 ;
char compteruniTgauchescore = '0';
char compteruniTgaucheremplacement = '0';
char compteruniTdroitescore = '0';
char compteruniTdroiteremplacement = '0';
char compterdizaiNdroitescore = '0';
char compterdizaiNdroitesremplacement = '0';
char compterdizaiNgauchescore = '0';
char compterdizaiNgaucheremplacement = '0';

char nombreGauche1[2] = { '0', '0' }; // Nombre gauche pour l'affichacge du score
char nombreDroite1[2] = { '0', '0' };

char nombreGauche0[2] = { '0', '0' }; // Nombre gauche pour l'affichage du remplacement
char nombreDroite0[2] = { '0', '0' };

char bluetooth_message = 0 ;


DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

void ScanDMD() {
  dmd.scanDisplayBySPI();
}

void setup(void) {
  Serial.begin(9600);
  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.setCursor(2,0);
  lcd.print("Hello World !");
  lcd.setCursor(0,1);
  lcd.print("community score board");
  lcd.setCursor(16,1);
  lcd.autoscroll();
  for (int x = 0; x < 6; x++)
    pinMode(x, INPUT_PULLUP);
  Timer1.initialize(200);
  Timer1.attachInterrupt(ScanDMD);
  dmd.clearScreen(true);
  Serial.begin(9600);
  delay(2000); //waiting time to start 
}

// function to increment digit right of left number
char compter_uniT_gauche(char ___message,bool opt) {
  int pressionB2 = digitalRead(b2);
  if(opt==1 || Serial.available())
  {
    if (!pressionB2 || ___message =='L')
    {
      compteruniTgauchescore++;
    }
    while (!pressionB2)
    pressionB2 = digitalRead(b2);
    if (compteruniTgauchescore > '9')
    compteruniTgauchescore = '0';
    return compteruniTgauchescore;
  }
  if(opt==0){
    if (!pressionB2)
    {
      compteruniTgaucheremplacement++;
    }
  while (!pressionB2)
    pressionB2 = digitalRead(b2);
  if (compteruniTgaucheremplacement > '9')
    compteruniTgaucheremplacement = '0';
  return compteruniTgaucheremplacement;
  }
}

// function to increment digit right of right number
char compter_uniT_droite(char __message,bool _opt) {
  int pressionB2 = digitalRead(b2);
  if(_opt==1 || Serial.available())
  {
    if (!pressionB2 || __message=='R' )
    {
      compteruniTdroitescore++;
    }
    while (!pressionB2)
      pressionB2 = digitalRead(b2);
    if (compteruniTdroitescore > '9')
      compteruniTdroitescore = '0';
  return compteruniTdroitescore;
  }
  if(_opt==0)
  {
      if (!pressionB2)
      {
        compteruniTdroiteremplacement++;
      }
      while (!pressionB2)
        pressionB2 = digitalRead(b2);
      if (compteruniTdroiteremplacement > '9')
        compteruniTdroiteremplacement = '0';
    return compteruniTdroiteremplacement; 
  }
}

// function to increment digit left of left number
char compter_dizaiN_gauche(char message,bool __opt) {
  int pressionB3 = digitalRead(b3);
  if(__opt==1 || Serial.available())
  {
    if (!pressionB3 || message=='G')
    {
      compterdizaiNgauchescore++;
    }
    while (!pressionB3)
      pressionB3 = digitalRead(b3);
    if (compterdizaiNgauchescore > '9')
      compterdizaiNgauchescore = '0';
    return compterdizaiNgauchescore;
  }
  if(__opt==0)
  {
    if (!pressionB3)
    {
      compterdizaiNgaucheremplacement++;
    }
    while (!pressionB3)
      pressionB3 = digitalRead(b3);
    if (compterdizaiNgaucheremplacement > '9')
      compterdizaiNgaucheremplacement = '0';
    return compterdizaiNgaucheremplacement;
  }
}

// function to increment digit left of right number
char compter_dizaiN_droite(char _message, bool ___opt) {
  int pressionB3 = digitalRead(b3);
  if(___opt==1 || Serial.available())
  {
    if (!pressionB3 || _message=='D')
    {
      compterdizaiNdroitescore++;
    }
    while (!pressionB3)
      pressionB3 = digitalRead(b3);
    if (compterdizaiNdroitescore > '9')
      compterdizaiNdroitescore = '0';
    return compterdizaiNdroitescore;
  }
  if(___opt==0)
  {
    if (!pressionB3)
    {
      compterdizaiNdroitesremplacement++;
    }
    while (!pressionB3)
      pressionB3 = digitalRead(b3);
    if (compterdizaiNdroitesremplacement > '9')
      compterdizaiNdroitesremplacement = '0';
    return compterdizaiNdroitesremplacement;
  }
}

// display numbers
// there are number for substitution
//and number for scoring
void afficher_remplacement() {
  dmd.clearScreen(true);
  dmd.selectFont(Arial_14);
  dmd.drawString(1, 0, nombreGauche0, 2, GRAPHICS_NORMAL);
  dmd.drawString(18, 0, nombreDroite0, 2, GRAPHICS_NORMAL);
  for (int y = 14; y > 10; y--) {
    if (y == 14) {
      dmd.writePixel(3, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(8, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(23, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(24, y, GRAPHICS_NORMAL, 1);
    }
    if (y == 13) {
      dmd.writePixel(4, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(7, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(22, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(25, y, GRAPHICS_NORMAL, 1);
    }
    if (y == 12) {
      dmd.writePixel(5, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(6, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(21, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(26, y, GRAPHICS_NORMAL, 1);
    }
  }
}
void afficher_score() {
  dmd.clearScreen(true);
  dmd.selectFont(Font_6x14);
  dmd.drawString(0, 0, nombreGauche1, 2, GRAPHICS_OR);
  for (int x = 14; x < 21; x++) {
    for (int y = 6; y <9 ; y++)
      dmd.writePixel(x, y, GRAPHICS_NORMAL, 1);
  }
  dmd.drawString(19, 0, nombreDroite1, 2, GRAPHICS_OR);
}

void loop() {
  if(!Serial.available())
    Serial.flush();
  bluetooth_message = Serial.read();
  int pressionB4 = digitalRead(b4);
  lcd.clear();
  lcd.setCursor(0,0);

  lcd.print("press yellow");
  lcd.setCursor(0,1);
  lcd.print("for help");

  if(digitalRead(b4)==LOW)
  {
    b4_pressed=true;
  }
  if(b4_pressed)
  {
    lcd.clear();
    lcd.setCursor(0,1);
    prevmillis=millis();
    while(millis()-prevmillis<2000)
      lcd.print("follow guide");
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("<press to change");
    lcd.setCursor(4,1);
    lcd.print("Displaying");

    b4_pressed = false ;//reset b4_pressed
  }


  if(digitalRead(b1)==LOW)
  {
    if(bascul3 == false)
    {
      bascul3 = true;
      buttonTimer = millis();
    }
    buttonPressDuration = millis() - buttonTimer;
    if((buttonPressDuration > long_press_time) && (longPressActive == false))
    {
      longPressActive = true ;
      bascul2 = 1 - bascul2;
    }
  }else {
  if(bascul3==true)
  {
    if(longPressActive == true)
      {
        longPressActive = false;
      }
    if((buttonPressDuration>100) && (buttonPressDuration<long_press_time))
    {
      bascul1=1-bascul1;
    }
  }
  bascul3 = false ;
  }
  if (bascul1 || Serial.available()) 
  {
    afficher_score();
    if((bascul2) || (bluetooth_message=='G') || (bluetooth_message=='L'))
    {
      nombreGauche1[0] = compter_dizaiN_gauche(bluetooth_message,bascul1);
      nombreGauche1[1] = compter_uniT_gauche(bluetooth_message,bascul1);
      dmd.writePixel(6, 0, GRAPHICS_NORMAL, 1);
      dmd.writePixel(7, 0, GRAPHICS_NORMAL, 1);
      //dmd.drawString(1, 0, nombreGauche1, 2, GRAPHICS_NORMAL);
    }
    if((!bascul2 )|| (bluetooth_message== 'D') || (bluetooth_message == 'R'))
    {
      nombreDroite1[0] = compter_dizaiN_droite(bluetooth_message,bascul1);
      nombreDroite1[1] = compter_uniT_droite(bluetooth_message,bascul1);
      dmd.writePixel(24, 0, GRAPHICS_NORMAL, 1);
      dmd.writePixel(25, 0, GRAPHICS_NORMAL, 1);
      //dmd.drawString(19, 0, nombreDroite1, 2, GRAPHICS_NORMAL);
    }
  }else if (!bascul1) {
    afficher_remplacement();
    if(bascul2)
    {
      nombreGauche0[0] = compter_dizaiN_gauche(bluetooth_message,bascul1);
      nombreGauche0[1] = compter_uniT_gauche(bluetooth_message,bascul1);
      dmd.writePixel(6, 0, GRAPHICS_NORMAL, 1);
      dmd.writePixel(7, 0, GRAPHICS_NORMAL, 1);
     // dmd.drawString(1, 0, nombreGauche0, 2, GRAPHICS_NORMAL);
    }
    else if(!bascul2)
    {
      nombreDroite0[0] = compter_dizaiN_droite(bluetooth_message,bascul1);
      nombreDroite0[1] = compter_uniT_droite(bluetooth_message,bascul1);
      dmd.writePixel(24, 0, GRAPHICS_NORMAL, 1);
      dmd.writePixel(25, 0, GRAPHICS_NORMAL, 1);
      //dmd.drawString(18, 0, nombreDroite0, 2, GRAPHICS_NORMAL);
    }
  }
}

Code explanation

We have many part in our code

First PART : Included Libraries

#include <SPI.h>
#include <DMD.h>
#include <TimerOne.h>
#include <Arial14.h>
#include <Arial_black_16.h>
#include <SystemFont5x7.h>
#include "Font_6x14.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>

Second PART : define variables

//Fire up the DMD library as dmd
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1

// set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27,16,2);  

// les variables pour la programmation de l'afficheur
int b1 = 2, b2 = 3, b3 = 4, b4 = 5;
bool b4_pressed = false ;
bool bascul1 = true;
bool bascul2 = true;
bool bascul3 = 0;
bool longPressActive = false ;
unsigned long buttonTimer = 0;
unsigned long buttonPressDuration = 0 ;
const unsigned long long_press_time = 1000; //time to consider a long press button
unsigned long prevmillis = 0 ;
char compteruniTgauchescore = '0';
char compteruniTgaucheremplacement = '0';
char compteruniTdroitescore = '0';
char compteruniTdroiteremplacement = '0';
char compterdizaiNdroitescore = '0';
char compterdizaiNdroitesremplacement = '0';
char compterdizaiNgauchescore = '0';
char compterdizaiNgaucheremplacement = '0';

char nombreGauche1[2] = { '0', '0' }; // Nombre gauche pour l'affichacge du score
char nombreDroite1[2] = { '0', '0' };

char nombreGauche0[2] = { '0', '0' }; // Nombre gauche pour l'affichage du remplacement
char nombreDroite0[2] = { '0', '0' };

char bluetooth_message = 0 ;


DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

void ScanDMD() {
  dmd.scanDisplayBySPI();
}

The following section is very important because we specified here how many P10 module we use as recommanded by the dmd library.

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1

We have also void ScanDMD() funtion to ensure the connection establishment.

THIRD PART : Defining the different personnalized funtions

// function to increment digit right of left number
char compter_uniT_gauche(char ___message,bool opt) {
  int pressionB2 = digitalRead(b2);
  if(opt==1 || Serial.available())
  {
    if (!pressionB2 || ___message =='L')
    {
      compteruniTgauchescore++;
    }
    while (!pressionB2)
    pressionB2 = digitalRead(b2);
    if (compteruniTgauchescore > '9')
    compteruniTgauchescore = '0';
    return compteruniTgauchescore;
  }
  if(opt==0){
    if (!pressionB2)
    {
      compteruniTgaucheremplacement++;
    }
  while (!pressionB2)
    pressionB2 = digitalRead(b2);
  if (compteruniTgaucheremplacement > '9')
    compteruniTgaucheremplacement = '0';
  return compteruniTgaucheremplacement;
  }
}

// function to increment digit right of right number
char compter_uniT_droite(char __message,bool _opt) {
  int pressionB2 = digitalRead(b2);
  if(_opt==1 || Serial.available())
  {
    if (!pressionB2 || __message=='R' )
    {
      compteruniTdroitescore++;
    }
    while (!pressionB2)
      pressionB2 = digitalRead(b2);
    if (compteruniTdroitescore > '9')
      compteruniTdroitescore = '0';
  return compteruniTdroitescore;
  }
  if(_opt==0)
  {
      if (!pressionB2)
      {
        compteruniTdroiteremplacement++;
      }
      while (!pressionB2)
        pressionB2 = digitalRead(b2);
      if (compteruniTdroiteremplacement > '9')
        compteruniTdroiteremplacement = '0';
    return compteruniTdroiteremplacement; 
  }
}

We made function to control each digit of numbers. the right way I found to control each digits is to define function to increment unit of left number char compter_uniT_gauche(char ___message,bool opt), another for unit of right number char compter_uniT_droite(char __message,bool _opt). I do the same for tens of each number char compter_dizaiN_gauche(char message,bool __opt); char compter_dizaiN_droite(char message,bool __opt).

// function to increment digit left of left number
char compter_dizaiN_gauche(char message,bool __opt) {
  int pressionB3 = digitalRead(b3);
  if(__opt==1 || Serial.available())
  {
    if (!pressionB3 || message=='G')
    {
      compterdizaiNgauchescore++;
    }
    while (!pressionB3)
      pressionB3 = digitalRead(b3);
    if (compterdizaiNgauchescore > '9')
      compterdizaiNgauchescore = '0';
    return compterdizaiNgauchescore;
  }
  if(__opt==0)
  {
    if (!pressionB3)
    {
      compterdizaiNgaucheremplacement++;
    }
    while (!pressionB3)
      pressionB3 = digitalRead(b3);
    if (compterdizaiNgaucheremplacement > '9')
      compterdizaiNgaucheremplacement = '0';
    return compterdizaiNgaucheremplacement;
  }
}

// function to increment digit left of right number
char compter_dizaiN_droite(char _message, bool ___opt) {
  int pressionB3 = digitalRead(b3);
  if(___opt==1 || Serial.available())
  {
    if (!pressionB3 || _message=='D')
    {
      compterdizaiNdroitescore++;
    }
    while (!pressionB3)
      pressionB3 = digitalRead(b3);
    if (compterdizaiNdroitescore > '9')
      compterdizaiNdroitescore = '0';
    return compterdizaiNdroitescore;
  }
  if(___opt==0)
  {
    if (!pressionB3)
    {
      compterdizaiNdroitesremplacement++;
    }
    while (!pressionB3)
      pressionB3 = digitalRead(b3);
    if (compterdizaiNdroitesremplacement > '9')
      compterdizaiNdroitesremplacement = '0';
    return compterdizaiNdroitesremplacement;
  }
}

As you can notice we have opt parameter in each function to specifiy which variable is indexed to display numbers. if opt==0 we control variable for substitution else we control variable for the scoring then opt==1.

// display numbers
// there are number for substitution
//and number for scoring
void afficher_remplacement() {
  dmd.clearScreen(true);
  dmd.selectFont(Arial_14);
  dmd.drawString(1, 0, nombreGauche0, 2, GRAPHICS_NORMAL);
  dmd.drawString(18, 0, nombreDroite0, 2, GRAPHICS_NORMAL);
  for (int y = 14; y > 10; y--) {
    if (y == 14) {
      dmd.writePixel(3, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(8, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(23, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(24, y, GRAPHICS_NORMAL, 1);
    }
    if (y == 13) {
      dmd.writePixel(4, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(7, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(22, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(25, y, GRAPHICS_NORMAL, 1);
    }
    if (y == 12) {
      dmd.writePixel(5, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(6, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(21, y, GRAPHICS_NORMAL, 1);
      dmd.writePixel(26, y, GRAPHICS_NORMAL, 1);
    }
  }
}
void afficher_score() {
  dmd.clearScreen(true);
  dmd.selectFont(Font_6x14);
  dmd.drawString(0, 0, nombreGauche1, 2, GRAPHICS_OR);
  for (int x = 14; x < 21; x++) {
    for (int y = 6; y <9 ; y++)
      dmd.writePixel(x, y, GRAPHICS_NORMAL, 1);
  }
  dmd.drawString(19, 0, nombreDroite1, 2, GRAPHICS_OR);
}

By convention, the setup function in Arduino does not require any input parameters and does not return any values, which is why it is defined as void setup(void) or simply void setup().

I have worked on the final project at the week of interface and application for bluetooth communication with smartphone.

I design the board I can use in elctronics design week and electronics production where I made it.

Bill of materials -

Below is a full BOM to give the cost of the project

Component Origin unit price Qtt Subtotal
LCD 1602 i2C PCF8574 Amazone $ 3.30 1 $ 3.30
P10 512 green LED Alibaba $ 19.00 1 $ 19.00
arduino nano board Alibaba $ 5.00 1 $ 5.00
Bluetooth HC-05 Alibaba $ 0.10 1 $ 0.10
Wi-fi module esp01 Alibaba $ 1.00 2 $ 2.00
IR sensor module Alibaba $ 0.40 2 $ 0.80
Bluetooth HC-05 Alibaba $ 0.10 1 $ 0.10
smd components pack Alibaba $ 2.00 1 $ 2.00
3D print Filament Alibaba $ 2.00 1 $ 2.00
MDF wood 3mm 1 piece Alibaba $ 1.90 1 $ 1.90

Files

code files