Skip to content

Final Project

I will record my Final projectct on this page.

Why I made this clock

When you stare at the hour hand on a clock, it seems to move incredibly slowly, like time is inching along… really slowly. But if you take a glance at the second hand, doesn’t it feel like time is actually speeding by – and fast? I created this piece to encourage everyone to think about how we experience time. Depending on the angle we choose to look at it, time can affect us in different ways. I hope people can come to appreciate that time is incredibly valuable and it always slips away before we know it. We should treasure every moment we have and live life to the fullest.

When you are one meter away from him, the pointer represents the hour hand; when you are closer to 80cm, the pointer represents the minute hand; when you are closer to 60cm, the pointer represents the second hand; when you are closer, the pointer will rotate at the fastest speed.

Final project sketch

Clock workflow

Preliminary appearance composition

1. Requirements and Planning

A. Project requirements

Design and construct a clock device that changes the time unit displayed (hour, minute, second, tenth of a second) based on the distance from the user to the device. Additionally, the clock incorporates an LED lighting feature where the flashing frequency of the lights accelerates in conjunction with the speed of the pointer movement.

B. Planning

Based on the concept of the project, I developed the following execution plan:

Step Description Status
1 Requirements and Planning Completed
2 Material Procurement In Progress
3 Circuit and Mechanical Design In Progress
4 Software Development and Testing Not Started
5 Structural component production (including CNC cutting, laser cutting and 3D printing) In Progressd
6 Hardware Assembly and Debugging Not Started
7 Make a video and PPT to introduce my project Not Started

2. Material Procurement

Based on the sketch, I sorted out the materials needed in the project and divided them into several categories: one is electronic and the other is appearance design.

A. Electronic Materials

Qty Description Price Link Notes
1 Seeed Studio XIAO ESP32 S3 Sens 13.99 $ https://www.seeedstudio.com/XIAO-ESP32S3-Sense-p-5639.html
1 Steper moto 1.99 $ https://www.taobao.com/list/item/583578061112.htm?spm=a21wu.10013406.taglist-content.5.447f2acccsEic1
1 Steper moto driver 1 $ https://m.tb.cn/h.gWZvqWg?tk=nhyqWE94OCF
1 RTC 7.06 $ https://item.taobao.com/item.htm?ali_refid=a3_430620_1006:1685292553:N:DfuTbet/BgdJzfXQ/lLTFw==:cc14539ae7bcf64e07083ef63c05bdbb&ali_trackid=1_cc14539ae7bcf64e07083ef63c05bdbb&id=778395485538&spm=a21n57.1.item.1&sku_properties=1627207:445472433
1 Ultrasonic distance sensor 3.18 $ https://item.taobao.com/item.htm?abbucket=6&id=777347838639&ns=1&priceTId=215042f717161008012131254e83a0&spm=a21n57.1.item.49.c1e0523ci6uarv
4 Neo pixel - ws2812B 4 $ https://m.tb.cn/h.gWj4WgQ?tk=nfpIWEjEj39

B. Structural parts and housing

Qty Description Price Link Notes
1 backplane About 5 $ from lab‘s storehouse
1 Hand of the clock About 1 $ from lab‘s storehouse
1 Frame of the clock About 5 $ from lab‘s storehouse
1 Surface of the clock (Acrylic) 6.46 $ https://item.taobao.com/item.htm?abbucket=16&id=653857006602&ns=1&priceTId=2147807017161014651355636e0535&skuId=5423499352215&spm=a21n57.1.item.5.4e9e523cEk1eZd

3. Circuit and Mechanical Design

I chose kicad as the design tool for my circuit board. KiCad is a free software for printed circuit board design, originally launched in 1992 by Frenchman Jean-Pierre Charras and now maintained by the KiCad development team. KiCad currently supports 23 language versions including English, French, German, Manhattan, Chinese, and Finnish.

and then download it

Schematic Editor & PCB Editor are the two interfaces that I need to use.

Then I need to install fab’s library first, this is the website to download thelibrary: https://gitlab.fabcloud.org/pub/libraries/electronics/kicad download the zip file, unzip the file, then install in schematic edditor. If the installation is successful, it will be show up in the schematic library.

A. Schematic part

then I start to draw schematic of my board.

Open the software, and you can first find out the modules and corresponding components I need to use from this location. After placing the basic modules and components, it looks like this:

I didn’t realize it at first, but later the stepper motor needs to be driven to work, so I added another stepper motor module.

Then I connected each pin to the corresponding position according to the data sheet of each module.

B. PCB board drawing part

then I switch to PCB editor After opening PCB editor, a window will appear, prompting you whether you want to update from schematic to PCB. Then you can see that all modules and components appear in the interface, and you can find some blue thin lines connecting them together. These lines represent that they need to be connected by circuits.

Then you need to use this tool to connect all the pins that need to be connected with circuits.

This step requires a lot of patience. You need to try various connection methods to prevent the circuits from overlapping. It took me a long time. With the help of my mentor Salman, I finally completed it:

The last step is to draw the outline of the entire PCB board. Interestingly, I feel that the PCB board I drew looks like a bird, so I drew the outline of a bird for this PCB board. Note that when drawing the outline, you need to switch to edge.cut mode and then start drawing.

After completing the drawing of the circuit diagram, the next step is to cut the PCB board.

Before cutting, we need to understand the G-code file

How does the entire process operate?

The operation of a CNC machine is dependent on “G-code,” and there are various methods available for producing this G-code.

G-code (also RS-274) is the most widely used computer numerical control (CNC) and 3D printing programming language. It is used mainly in computer-aided manufacturing to control automated machine tools, as well as for 3D-printer slicer applications. The G stands for geometry. G-code has many variants.

G-code instructions are provided to a machine controller (industrial computer) that tells the motors where to move, how fast to move, and what path to follow. The two most common situations are that, within a machine tool such as a lathe or mill, a cutting tool is moved according to these instructions through a toolpath cutting away material to leave only the finished workpiece and/or an unfinished workpiece is precisely positioned in any of up to nine axes[1] around the three dimensions relative to a toolpath and, either or both can move relative to each other. The same concept also extends to noncutting tools such as forming or burnishing tools, photoplotting, additive methods such as 3D printing, and measuring instruments.

A/ method involves utilizing the “Mods CE” utility to directly create the G-code, which necessitates the use of PNG or SVG images due to their non-lossy resolution quality;

B/One can input Gerber files into the CNC machine’s corresponding software to facilitate the automatic generation of the code.

First I need to setup the board

then export the Gerber file. open the website:https://gerber2png.fablabkerala.in/

Convert gerber files to PNG files, This is a circuit file This is a drilling file This is the outline file

then open the web site:https://modsproject.org/?program=programs/machines/G-code/mill%202D%20PCB

Convert PNG files to G-code, import the PNG image into Mods CE and set the parameters as shown below. You need to adjust the parameters of these two parts.

click the “calculate” which is on the final of the path. The whole trace is looking like: then I get the “nc” file the nc file is the G-code file.

C. PCB manufacturing part

After preparing the files, I can start cutting the pcb The left one is 0.4mm V-bit and right one is 0.8mm drill. there are three file:

1/ top_layer_drills_1000dpi.png.nc is using 0.4mm V-bit.

2/ top_layer_outline_1000dpi.png.nc and top_layer_traces_1000dpi.png.nc are using 0.8mm drill.

Then you need to set the origin of the tool head, and use this tool to move the tool head to the corner of the processing area to ensure that it does not exceed the area of ​​the copper plate.

Alt text

Alt text

Then clear the X and Y axes and retain the Z axis

Test whether the software interface buttons can control the operation of the device: click Emergency Reset Alt text Coordinates cleared Alt text Regenerate toolpath Alt text Click to start Alt text

cut the outline of the board Import the frame file, the same operation as cutting the trace file

here we are, we go the final board: Alt text

D. PCB soldering

I have prepared the necessary electronic components first

Alt text

Alt text

Alt text After completion, we need to use a multimeter to detect whether there is a short circuit in the circuit

As shown in the picture below, turn the middle knob to point “Ω”, then click the blue button to switch modes. Once you know the symbol of the signal, you can start testing. Put the two red and black heads in contact with each other. A buzzing sound will sound, indicating that the circuit is connected. .

Alt text

After testing the circuit, no abnormalities were found.

At this point, the board welding is completed.

E. PCB testing

The teacher said that the load of the stepper motor will be relatively large and this part can be tested first. Therefore, I soldered the male pins to the 4 interfaces of the stepper motor and connected them to the PCB board according to the motor driver instructions (as shown below). Alt text

The picture comes from the instruction manual provided by the motor drive merchant Alt text Alt text

I was ready, turned on the power, and found that the motor moved once, then stopped, and the motor driver chip was still smoking. I immediately disconnected the power and found that the chip had been burned.

The inspection found that: 1. The stepper motor driver did not have a heat sink; 2. The four lines of the motor were connected in the wrong order; resulting in a short circuit or error in the circuit, causing the chip to overheat and burn out. Alt text

So I need to make the PCB again

start from schematic.

Before this, the tutor suggested that we can do experimental tests with strips first, and then manufacture PCB boards.

Started a long testing journey

I started using a breadboard to test how the motor works properly

At the same time I also restructured the schematic

Alt text

Alt text

Alt text

and soldering

Alt text

After the test was completed, it was discovered that there was a new method that did not require the use of the RTC module, so the entire circuit was also modified, and this board could no longer be used. Until now, many PCB boards have been sacrificed. The following is Their collection, these PCB boards really took me too much time to make

Alt text

and this is the final test demo:

Alt text

Next I need to integrate this feasible demo into a PCB board.

  1. schematic The following are the components I will use

Alt text Alt text Alt text Alt text Alt text

Connect all pins according to the demo wiring method to get the final schematic Alt text

Switch to PCB board design and connect all the prompted connections with actual circuits. We get the following circuit diagram, and then we use “Check-Design Rule Check” in the menu bar to test whether there is a problem with the PCB board. Alt text

Alt text

then Convert Gerber to PNG & Convert PNG to G-code

This conversion method was mentioned in week8, you can see the detailed operation there.

Then I can finally start manufacturing again.

Then quickly complete the welding,

Alt text

Alt text

finally can jump to the next part.

4. Programming

First of all, I need to clarify what I need to achieve when writing this code:

  1. It needs to run on ESP32;

  2. Use Wi-Fi to synchronize time;

  3. Control the stepper motor through ultrasonic sensor;

  4. Control the brightness of the LED strip.

Converted into a code structure, it includes structures such as

A. Setting up network connections;

B. LED configuration;

C. Timer configuration;

D. Implementing multi-tasking.

A. Setting up network connections;

Find all required library files

#include<Arduino.h>
#include <WiFi.h>
#include "Ultrasonic.h"
#include <String.h>
#include "FastLED.h"

First, the Arduino core library is introduced, the WiFi library is used for network functions, the Ultrasonic library is used for reading ultrasonic distance sensors, String is used for string processing, and FastLED is used to control LED light strips.

Wi-Fi settings

const char *ssid = "x.factory";             // Wi-Fi SSID
const char *password = "make0314";          // Wi-Fi 密码

The SSID and password for connecting to the Wi-Fi network are set here. Note that a 2.4G network is required here. If not, it needs to be set in advance in the network settings.

Here is the setup tutorial I found

B. LED configuration

LED strip configuration

#define NUM_LEDS 100            
#define LED_DT 9                
#define LED_TYPE WS2812         
#define COLOR_ORDER GRB 

This code defines the number of LEDs used, I have set up 100 lights here , the data pins(I define GPIO9) connected to the ESP32, the LED type and color sequence.

LED brightness and color configuration

uint8_t max_bright = 128;       
CRGB leds[NUM_LEDS];            
CRGB myRGBcolor(50,50,50);

Set the maximum brightness of the LED (after debugging, here I set the brightness of 128) and define a CRGB array to control each LED of the LED strip. A color variable myRGBcolor is also defined.

Other hardware configurations

Ultrasonic ultrasonic(4);
const int dirPin = 1;
const int stepPin = 2;
...

Initialize the ultrasonic sensor, direction and stepper pins of the stepper motor.

C. Timer configuration

Time-related definitions and variables

#define NTP1 "ntp1.aliyun.com"
...
String minute;
String second;
String hour;

Defines the NTP server address and a string variable used to store the time.

D. Implementing multi-tasking

Multitasking functions

xTaskOne 
xTaskTwo

These two functions are FreeRTOS tasks, used to loop to measure distance and control the brightness of LED light strips respectively.

Setup function

void setup() {
  ...
  WiFi.begin(ssid, password);
  ...
  configTime(8 * 3600, 0, NTP1, NTP2, NTP3);
  ...
  xTaskCreate(...);
}

In the setup function, the serial port is initialized, the stepper motor control pin is set, the Wi-Fi network is connected, the network time is obtained, and the tasks described earlier are created.

There are also some other functions used

loop function

This function includes time synchronization and controlling the rotation of the stepper motor based on the ultrasonic sensor readings to simulate the movement of the hour, minute and second hands of the clock.

onestep is used for single-step operation of stepper motors. fast is used to quickly rotate a stepper motor one full step.

getCurrentAngle_F and getCurrentAngle_S are used to calculate the angle of stepper motor rotation.

setClock is used to read network time and update time variables.

After many times of testing and integration, the final code looks like this

#include<Arduino.h>
#include <WiFi.h>
#include "Ultrasonic.h"
#include <String.h>
#include "FastLED.h"           

//wifi config
const char *ssid = "x.factory";             // WIFI account
const char *password = "make0314";         // WIFI password

#define USE_MULTOCRE 0
#define NUM_LEDS 100            // set the num of led
#define LED_DT 9                // LED pin
#define LED_TYPE WS2812         // LED Light Strip Model
#define COLOR_ORDER GRB         

uint8_t max_bright = 128;       // LED max bright

CRGB leds[NUM_LEDS];            

//CRGB ColorName方法定义颜色                        
CRGB myRGBcolor(50,50,50);   // myRGBcolor(rValue,gValue,bValue)
                            // rValue: 0 - 255
                            // gValue: 0 - 255
                            // bValue: 0 - 255

Ultrasonic ultrasonic(4);
const int dirPin = 1;  // dir pin
const int stepPin = 2; // step pin
int num = 0;
const int STEPS_PER_REV = 1;
const float stepAngle = 0.9; // Step angle of stepper motor
const int microstepping = 1; // 1 = full step
long stepCount_S = 0;  // num of step fow slow

long stepCount_F = 0;  // num of step fow fast
long RangeInCentimeters = 1000;

int remember_min;  //rec min
int remember_sec;  // rec sec
int remember_hour; // rec hour

// Time info
#define NTP1 "ntp1.aliyun.com"
#define NTP2 "ntp2.aliyun.com"
#define NTP3 "ntp3.aliyun.com"


String minute;
String second;
String hour;

/*
Task one
  This function continuously measures the distance using an ultrasonic sensor,
  calculates the average of three measurements, and then prints the average distance.
*/
void xTaskOne(void *xTask1){

  int rangeMeasurements[3]; // Storage of three measurements
  int sum = 0; // For totalizing measured values

  while (1) {
    for (int i = 0; i < 3; i++) {  // Perform three measurements
      rangeMeasurements[i] = ultrasonic.MeasureInCentimeters(); // Get the distance in centimeters from the ultrasonic sensor
      sum += rangeMeasurements[i]; // Add the current measurement to the sum
      delay(250); 
    }

    // Calculation of the average value
    int averageRange = sum / 3;
    RangeInCentimeters = averageRange;

    sum = 0;
    delay(500); 
  }
  vTaskDelete(NULL);
}

/*
Task two
  This function is responsible for controlling the brightness of an LED strip.
*/
void xTaskTwo(void *xTask2){

  while (1) {
    // increase brightness
    for (double i = 20;i<150;i+=0.5){
      FastLED.setBrightness(i);     
      myRGBcolor.r = 50; 
      myRGBcolor.b = 50; 
      myRGBcolor.g = 50;
      fill_solid(leds, NUM_LEDS, myRGBcolor);   
      FastLED.show();      
      delay(10);  
    }
    // Reduced brightness
    for (double i = 150;i>20;i-=0.5){
      FastLED.setBrightness(i);     
      myRGBcolor.r = 50; 
      myRGBcolor.b = 50;
      myRGBcolor.g = 50;
      fill_solid(leds, NUM_LEDS, myRGBcolor);   
      FastLED.show();      
      delay(10);  
    }
  }
  vTaskDelete(NULL);
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(500); 

  // Set the pin modes for the stepper motor control pins
  pinMode(stepPin,OUTPUT); 
  pinMode(dirPin,OUTPUT);

  // Set WiFi to station mode and attempt to connect to the specified network
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  // Set WiFi to station mode and attempt to connect to the specified network
  LEDS.addLeds<LED_TYPE, LED_DT, COLOR_ORDER>(leds, NUM_LEDS);  

  // Set the brightness of the LED strip to the maximum value
  FastLED.setBrightness(max_bright);                           

  // Wait for the WiFi connection to be established
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected!");
  configTime(8 * 3600, 0, NTP1, NTP2, NTP3);


#if !USE_MULTCORE
  // Create task one with a specified name, stack size, priority, and no parameters
  xTaskCreate(
    xTaskOne,/* Task function. */
    "TaskOne",/* String with name of task. */
    4096,/* Stack size in bytes.*/
    NULL,/* parameter passed as input of the task */
    1,/* priority of the task.(configMAx PRIORITIES - 1 being the highest, and @ being the lowest.) */
    NULL);/* Task handle.*/


  // Create task two with a specified name, stack size, priority, and no parameters
  xTaskCreate(
    xTaskTwo,/* Task function.*/
    "TaskTwo",/* String with name of task. */
    4096,/* Stack size in bytes.*/
    NULL,/* parameter passed as input of the task */
    2,/* priority of the task.(configMax PRIORITIES - 1 being the highest, and  being the lowest.) */
    NULL);  /* Task handle.*/

#else
  xTaskCreatepinnedToCore(xTaskOne,"TaskOne",4096,NULL,1,NULL,0);
  xTaskCreatepinnedToCore(xTaskTwo,"TaskTwo",4896,NULL,2,NULL,1);

#endif


}

void loop() {

  int current_angle;    // Variable to store the current angle of the actuator in 360 degree
  int getCurrentAngle; // Variable to store the total angle of the actuator

  if(RangeInCentimeters  > 300){
    setClock(); // Function to read the current time
    int num_hour = hour.toInt(); // Convert the current hour to an integer

    if(num_hour>12){
      num_hour = num_hour - 12; // Convert to 12-hour format if greater than 12
    }

    // Check if the current hour is different from the remembered hour
    if(num_hour != remember_hour){
      remember_hour = num_hour; // Update the remembered hour
      int target_angle = num_hour * 30; // Calculate the target angle based on the hour
      Serial.print("hour is, ");  
      Serial.println(num_hour);
      getCurrentAngle = getCurrentAngle_S(); // Get the current angle
      current_angle = getCurrentAngle % 360; // Update the current angle, wrapping around at 360 degrees

      // Determine the direction to rotate and perform the rotation
      if(target_angle > current_angle){
        for(int i = 0; i < (target_angle - current_angle)/0.9; i++){
          onestep();
          delay(10);
        }
      }else{
        for(int i = 0; i < (target_angle + 360 - current_angle)/0.9; i++){
          onestep();
        }
      }
      // getCurrentAngle = getCurrentAngle_S();
      // current_angle = getCurrentAngle % 360;
      // Serial.print("current angle is ");
      // Serial.println(current_angle);
    }

  }
  else if(RangeInCentimeters  < 300 && RangeInCentimeters  > 50){ // Minutes logic
    remember_hour = 60;
    setClock();
    int num_min = minute.toInt();
    if(num_min != remember_min){
      remember_min = num_min;
      int target_angle = num_min * 6;
      Serial.print("min is, ");
      Serial.println(num_min);
      getCurrentAngle = getCurrentAngle_S();
      current_angle = getCurrentAngle % 360;

      if(target_angle > current_angle){
        for(int i = 0; i < (target_angle - current_angle)/0.9; i++){
          onestep();
          delay(10);
        }
      }else{
        for(int i = 0; i < (target_angle + 360 - current_angle)/0.9; i++){
          onestep();
        }
      }
    }
 } else{ // Seconds logic
  remember_min = 70;
  remember_hour = 60;
  setClock();
  int num_sec = second.toInt();

  if(num_sec != remember_sec){
    remember_sec = num_sec;
    int target_angle = num_sec * 6;
    Serial.print("sec is, ");
    Serial.println(num_sec);
    getCurrentAngle = getCurrentAngle_S();
    current_angle = getCurrentAngle % 360;

    if(target_angle > current_angle){
      for(int i = 0; i < (target_angle - current_angle)/0.9; i++){
        onestep();
        // Serial.print(i);
        delay(10);
      }
    }else{
      for(int i = 0; i < (target_angle + 360 - current_angle)/0.9; i++){
        onestep();
      }
    }
    getCurrentAngle = getCurrentAngle_S();
    current_angle = getCurrentAngle % 360;
    Serial.print("current angle is ");
    Serial.println(current_angle);
  }

 }


}

void onestep(){

  digitalWrite(dirPin,HIGH); 
  digitalWrite(stepPin,HIGH); 
  delayMicroseconds(1000); 
  digitalWrite(stepPin,LOW); 
  delayMicroseconds(1000); 
  stepCount_S++; // update the num of step
  // delay(1000); 
}
void fast(){
digitalWrite(dirPin,HIGH);

  for(int x = 0; x < STEPS_PER_REV ; x++) {
    digitalWrite(stepPin,HIGH);
    delayMicroseconds(1000);
    digitalWrite(stepPin,LOW);
    delayMicroseconds(1000);
    stepCount_F++; 
  }

}


float getCurrentAngle_F() {
  return stepCount_F * (stepAngle / microstepping);
}
float getCurrentAngle_S() {
  return stepCount_S * (stepAngle / microstepping); 
}


//time_t now;
void setClock()
{
  struct tm timeInfo; 
  if (!getLocalTime(&timeInfo))
  { 
    Serial.println("Failed to obtain time");
    return;
  }
  //Serial.print(asctime(&timeInfo)); 
  // String date = WDAY_NAMES[timeInfo.tm_wday];
  // Serial.println(date.c_str());
  // sprintf_P(buff1, PSTR("%04d-%02d-%02d %s"), timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, WDAY_NAMES[timeInfo.tm_wday].c_str());
  // String shuju = String(timeInfo.tm_year + 1900); 
  // shuju += "-";
  // // shuju += timeInfo.tm_mon + 1; 
  // // shuju += "-";
  // shuju += timeInfo.tm_mday; 
  // shuju += " ";
  // shuju += timeInfo.tm_hour; 
  // shuju += ":";
  // shuju += timeInfo.tm_min;
  // shuju += ":";
  // shuju += timeInfo.tm_sec;
  // shuju += " ";
  // // shuju += WDAY_NAMES[timeInfo.tm_wday].c_str(); 
  // Serial.println(shuju.c_str());
  minute = timeInfo.tm_min;
  second = timeInfo.tm_sec;
  hour = timeInfo.tm_hour;
}

5. Appearance design

For the detailed process of appearance design, please see week03& week05 Alt text Alt text Alt text Alt text Alt text

6. Integration

The following is my assembly process

Alt text

The final effect is as follows