Skip to main content

3rd Test

Objectives

Design and build an innovative 7-segment display using 7 servomotors instead of traditional LEDs. Each segment of the digit is represented by a dedicated servo. The system must display digits from 0 to 9, then from 9 back to 0, with one second per change.


Technical Objectives

  • Use 7 servomotors, one for each segment of the digit.
  • Display digits from 0 to 9, then from 9 to 0 automatically.
  • Respect a 1-second interval between digit changes.
  • Do not use blocking functions such as delay() in the Arduino code.
  • Power the circuit with lithium batteries.
  • Control the servos using a standalone ATmega328P microcontroller (no Arduino boards allowed in the final design).
  • Design the full schematic and PCB using KiCad**, including:
    • Schematic diagram
    • PCB layout
    • 3D render of the board
  • Design and 3D print a protective and functional casing to house the display and servos.

Additional Notes

  • We may use a PCA9685 module to simplify PWM control for the servos.
  • The use of Arduino boards or breadboards in the final assembly is strictly forbidden.

Summary


Files available for download

Download the KiCad and Arduino files by following these links :

KiCad

Arduino


Materials

CategoryComponentQuantityNotes
MicrocontrollerATmega328P1Used as standalone (no Arduino board)
Clock16 MHz Crystal Oscillator1Paired with two 22 pF capacitors
22 pF Capacitor2For the crystal oscillator
ServomotorsSG90 Micro Servo or equivalent7One per digit segment
PWM ModulePCA96851For controlling servos and LEDs via I2C
LED DisplayRed/Green/Blue LEDs7+47 for segments, 4 for battery level indicator
Current-limiting ResistorsMultipleFor LEDs and transistor bases
Transistors2N2222A (or 2N2219 for simulation)7For LED brightness modulation
Battery Pack3.7 V, 3 Ah Lithium Cells42S2P configuration → 7.4 V, 6 Ah total
Battery ChargingTP4056 Charger Modules4One per lithium cell
Voltage RegulationLM2596 Buck Converter1Steps down 7.4 V to 5 V
Voltage DividerResistors (custom values)2For battery voltage measurement via ADC
Programming Interface6-pin FTDI Header1To upload code to ATmega328P
Reset CircuitPush Button + Pull-up Resistor1For manual microcontroller reset
Bootloader IndicatorLED + Resistor1Connected to Lbuilt pin to confirm bootloader presence
Enclosure3D Printed Casing1Designed in Fusion 360
Connectors/WiringJumper Wires, Headers, Terminals, etc.VariousFor electrical connections

Process

Power Supply Design

We are using a power supply system based on rechargeable lithium batteries.

To achieve this, we are using four 3.7V, 3Ah lithium cells, arranged in a 2S2P configuration:

  • 2 cells in series, then duplicated in parallel.

Battery Pack Configuration

  • Nominal voltage: 7.4V
  • Capacity: 6Ah
  • Fully charged voltage: 8.4V

Voltage Regulation

The battery pack powers the input of a buck converter (LM2596), which regulates the voltage down to:

  • 5V output
  • Max current: 3A

Battery Monitoring

A voltage divider is used to monitor the battery voltage.

  • At 8.4V (fully charged), the measured voltage is 5V
    → This is the maximum allowed input for the ATmega328P ADC (Analog to Digital Converter).

This monitoring function allows us to:

  • Estimate the battery state of charge
  • Ensure safe and optimal performance of the system

Power Distribution

The regulated 5V output is then distributed across the different DC buses of the circuit.


Hardware design


Circuit principal – Architecture matérielle

Img1

Img1Img1
Img3Img2

The ATMega328P microcontroller is powered by a 5V DC supply provided by a Buck converter (LM2596), represented in the schematic by the connector located near the transistor. A 16 MHz crystal oscillator, along with two 22 pF capacitors, provides the clock signal for the microcontroller.

Img1Img1

LED D1, connected to the Lbuilt pin, is used to verify the presence of the bootloader on the microcontroller. The push-button SW1, together with a pull-up resistor, enables manual reset of the microcontroller.

Img1Img1

A 6-pin header allows connection to an FTDI programmer for uploading code to the microcontroller. A second 5-pin header provides I2C communication and power supply to the PCA9685 module.

Img1Img1

The battery monitoring circuit is based on a resistive voltage divider, which taps a fraction of the battery voltage. Using this voltage and the known divider ratio, the battery charge level can be estimated and displayed through LEDs D23 to D26.

Img1Img1

An additional network composed of transistors and LEDs has been implemented to introduce an innovative feature. The LEDs forming digit segments will vary in brightness depending on the signal applied to the bases of 2N2219 transistors (used in simulation; 2N2222A will be used in the final implementation). These transistors, controlled by the PCA9685 module, allow the brightness of the LEDs connected to their collectors to be modulated.

Img1Img1

Current-limiting resistors for both the LEDs and transistor bases have been carefully calculated to ensure stable and optimal operation of the circuit.


Lithium Battery Charging System

Img3

The charging system consists of four lithium battery charger modules (such as TP4056 or similar), each dedicated to an individual battery. Each module’s output is connected directly to the terminals of its respective lithium cell.

  • Module Inputs: All inputs are connected in parallel to the 5 V power supply, which was also used during Test 1 and Test 2 to charge the batteries.
  • Module Outputs: Each output is routed to a separate lithium battery, enabling independent and balanced charging.

This modular approach ensures:

  • Simplified wiring
  • Independent protection per battery
  • Reduced risk of overcharging or imbalance between cells

It provides a safe, scalable, and efficient charging solution for powering the system.


Software design

This Arduino code controls an innovative physical 7-segment display combining servomotors, LEDs with fade effects, and a battery indicator.

Code Structure

Libraries and Configuration

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
  • Wire.h: I2C communication with the PCA9685
  • Adafruit_PWMServoDriver.h: PCA9685 module control
Servo Configuration
#define SERVO_MIN 100   // 0° position
#define SERVO_MAX 500 // 180° position
int angleOn = 90; // Angle for active segment
int angleOff = 0; // Angle for inactive segment

These values define the PWM range for servomotors. Values 100-500 correspond to pulse widths for 0° and 180°.

PCA9685 Channel Mapping

const int segments[7] = {0, 1, 2, 3, 4, 5, 6};        // Servo segments A-G
const int ledChannels[7] = {8, 9, 10, 11, 12, 13, 14}; // LED segments A-G
const int batteryLEDs[4] = {9, 10, 11, 12}; // Battery indicator LEDs

Important: Battery LEDs use the same channels as some segment LEDs (9-12).

Pattern Tables

Servomotor Table
const bool servos[12][7] = {
{1,0,0,1,0,1,1}, // Pattern 0
{0,1,1,0,1,0,1}, // Digit 0
{1,1,1,1,0,1,1}, // Digit 1
// ... etc
};

Each row represents a digit (0-11), each column a segment (A-G).

  • 1 = segment activated (servo at 90°)
  • 0 = segment deactivated (servo at 0°)
LED Table
const bool leds[12][7] = {
// Identical to servo table for now
// But can be modified independently
};

Allows separate control of lighting and physical positioning.

Main Functions

Setup - Initialization

void setup() {
pwm.begin();
pwm.setPWMFreq(50); // 50Hz for servos

// Initialize segment states
for (int i = 0; i < 7; i++) {
currentSegmentState[i] = leds[currentDigit][i];
previousSegmentState[i] = currentSegmentState[i];
}

displayDigit(currentDigit);
currentDigit += 1;
}

Main Loop - Multi-task Management

Digit Change Management
if (currentMillis - previousMillis >= interval) {
// Save previous states for fade
for (int i = 0; i < 7; i++) {
previousSegmentState[i] = currentSegmentState[i];
currentSegmentState[i] = leds[currentDigit][i];
}

displayDigit(currentDigit); // Move servos

fadeStartTime = currentMillis;
fadeActive = true; // Start LED fade

// Back-and-forth logic (0→11→0)
currentDigit += direction;
if (currentDigit > 11) {
currentDigit = 11;
direction = -1;
} else if (currentDigit < 0) {
currentDigit = 0;
direction = 1;
}
}

Timing: Change every 1 second (interval = 1000)

LED Fade Animation
if (fadeActive) {
updateLEDFade(currentMillis);
}

The fade runs in parallel for 1 second while servos move.

Battery Monitoring
if (currentMillis - lastBatteryCheck >= batteryCheckInterval) {
lastBatteryCheck = currentMillis;
updateBatteryLevel();
}

Timing: Check every 500ms for smooth monitoring.

Detailed Functions

displayDigit() - Servo Control

void displayDigit(int number) {
for (int i = 0; i < 7; i++) {
if (servos[number][i]) {
setServoAngle(segments[i], angleOn); // 90°
} else {
setServoAngle(segments[i], angleOff); // 0°
}
}
}

Instantly positions all servos according to the digit pattern.

updateLEDFade() - LED Animation

Fade Logic
void updateLEDFade(unsigned long currentMillis) {
unsigned long elapsed = currentMillis - fadeStartTime;

if (elapsed >= fadeDuration) {
// Fade complete - final states
fadeActive = false;
for (int i = 0; i < 7; i++) {
if (currentSegmentState[i]) {
pwm.setPWM(ledChannels[i], 0, 4095); // 100%
} else {
pwm.setPWM(ledChannels[i], 0, 0); // 0%
}
}
} else {
// Fade in progress
float progress = (float)elapsed / fadeDuration; // 0.0 → 1.0

for (int i = 0; i < 7; i++) {
int brightness = 0;

if (currentSegmentState[i] && !previousSegmentState[i]) {
// FADE IN: 0 → 4095
brightness = (int)(4095 * progress);
} else if (!currentSegmentState[i] && previousSegmentState[i]) {
// FADE OUT: 4095 → 0
brightness = (int)(4095 * (1.0 - progress));
} else if (currentSegmentState[i] && previousSegmentState[i]) {
// STAYS ON
brightness = 4095;
} else {
// STAYS OFF
brightness = 0;
}

pwm.setPWM(ledChannels[i], 0, brightness);
}
}
}

Fade Types:

  • Fade IN: Segment turning on (0% → 100%)
  • Fade OUT: Segment turning off (100% → 0%)
  • Constant: Segment that doesn't change state

updateBatteryLevel() - Battery Indicator

ADC Reading and Conversion
void updateBatteryLevel() {
int adcValue = analogRead(batteryPin); // 0-1023
float voltage = (adcValue / 1023.0) * 5.0; // 0-5V
float batteryPercent = (voltage / maxVoltage) * 100.0; // % based on 4.9V max

// Constrain 0-100%
if (batteryPercent > 100.0) batteryPercent = 100.0;
if (batteryPercent < 0.0) batteryPercent = 0.0;
LED Display Logic
  // Turn off all battery LEDs
for (int i = 0; i < 4; i++) {
pwm.setPWM(batteryLEDs[i], 0, 0);
}

// Turn on according to level
if (batteryPercent >= 75.0) {
// 4 LEDs: channels 9,10,11,12
for (int i = 0; i < 4; i++) {
pwm.setPWM(batteryLEDs[i], 0, 4095);
}
} else if (batteryPercent >= 50.0) {
// 3 LEDs: channels 10,11,12
for (int i = 1; i < 4; i++) {
pwm.setPWM(batteryLEDs[i], 0, 4095);
}
} else if (batteryPercent >= 25.0) {
// 2 LEDs: channels 11,12
for (int i = 2; i < 4; i++) {
pwm.setPWM(batteryLEDs[i], 0, 4095);
}
} else if (batteryPercent > 0.0) {
// 1 LED: channel 12
pwm.setPWM(batteryLEDs[3], 0, 4095);
}
}

Display Levels:

  • ≥75%: 🟢🟢🟢🟢 (4 LEDs)
  • 50-74%: 🔴🟢🟢🟢 (3 LEDs)
  • 25-49%: 🔴🔴🟢🟢 (2 LEDs)
  • 1-24%: 🔴🔴🔴🟢 (1 LED)
  • 0%: 🔴🔴🔴🔴 (0 LED)

Technical Features

Timing and Performance

  • Display: Change every 2 seconds
  • LED Fade: 1 second smooth transition
  • Battery: Check every 500ms
  • PWM: 50Hz for servos, 12-bit resolution (0-4095) for LEDs

Memory Management

  • Non-blocking: Uses millis() instead of delay()
  • Multi-tasking: Simultaneously handles servos, LEDs, and battery
  • Saved States: Enables intelligent fade transitions

Flexibility

  • Separate Tables: Independent servo/LED control
  • Customizable Patterns: 12 configurable digits
  • Extensible: Easy to add new effects

Practical Usage

This code creates a unique 7-segment display that combines:

  1. Physical movement of segments via servomotors
  2. Dynamic lighting with smooth fade transitions
  3. Real-time battery monitoring
  4. Continuous animation with back-and-forth effect

The result is a spectacular display where segments move mechanically while progressively lighting up!


Pictures

Img1Img1
Img3Img2

See more...


Videos


See more...


3D Modeling & Enclosure Design

The enclosure was designed in Fusion 360 and 3D printed to house all the components.

Img1Img1
Img3Img2

Img1Img1
Img3Img2