Final Project Documentation

Reginald Itiseng - Fab Academy 2025

Project Presentation Video

A 1-minute introduction to the Electric Scooter project.


Introduction

My Final Project

This section introduces my final project idea and begins the documentation process.
I will update this page as I make progress throughout the Fab Academy course. Below is a brief description of my proposed final project.

Electric Scooter

The Electric Scooter project aims to design and build a compact, efficient, and eco-friendly personal transportation device. The scooter will feature a lightweight frame, a brushless DC motor, and a rechargeable battery system to provide reliable urban mobility. Key features include electronic speed control, regenerative braking, and a digital dashboard for real-time speed and battery monitoring. The design will focus on safety, portability, and ease of use, making it suitable for short commutes and campus travel. By integrating smart electronics and robust mechanical components, this project demonstrates practical skills in digital fabrication, embedded systems, and sustainable design.

Below is a concept sketch of the Electric Scooter.
This illustration will be updated as the project develops.

Electric Scooter Concept

Background Research - Who's done what beforehand?

Before starting my final project, I conducted extensive research to understand existing solutions, technologies, and design principles related to electric scooters. This background research helped me identify gaps in the market and opportunities for innovation.

Click on the images to view the sources.

Jay Dhariwal's final project
Jay Dhariwal's final project
Bicycle converted to an electric bike.
Ivan Gonzalez's electric scooter (FabAcademy 2020)
Ivan Gonzalez's electric scooter (FabAcademy 2020)
Reclaiming an old broken electronic scooter
Final Project: Building a Derp-e-bike!
                    Posted on December 16, 2022 by Maxwell Yun
Electric bike conversion
Final Project: Building a Derp-e-bike! Posted on December 16, 2022 by Maxwell Yun
electric cart
Electric go cart (Fab Academy 2019)
Hakan Zayin's electric go cart final project
self balancing electric skateboard
TARIGOPPULA SAI ADITYA VYNATHEYA(Fab Academy 2018)
Self balancing Electric scateboard.

Jay Dhariwal's Final Project (2019): Jay was motivated by the urgent need for electric vehicles in India, aiming to reduce carbon emissions and dependence on imported oil. His project focused on documenting the design and fabrication of a low-cost, easy-to-build electric vehicle by converting a standard bicycle into an electric bike, with plans to add solar power and a sun-shade for thermal comfort. Jay's approach included using a modular design, starting with an e-bike conversion kit and solar system, and then developing custom components such as a motor controller PCB and smartphone integration for sensor data. He highlighted the use of spiral development, the importance of hands-on learning in electronics and fabrication, and the potential for extending the project to more advanced electric vehicles or adding features like air quality sensing and autonomous driving.

Ivan Gonzalez's Electric Scooter (2020): Ivan reclaimed and rebuilt a broken commercial electric scooter, emphasizing repair and sustainability. He detailed the process of diagnosing faults, replacing damaged electronics, and designing custom PCBs for improved control. Ivan's documentation shows the importance of reverse engineering, iterative prototyping, and using open-source tools for both hardware and software.

Maxwell Yun's Derp-e-bike (2022): Maxwell's project used 10S lithium-polymer batteries from two Turnigy packs connected in series. He selected an off-the-shelf Flipsky VESC 6 as the motor controller and a T-Motor P80 II KV100 outrunner motor, originally designed for agricultural quadcopters. This motor provides high torque at low RPM, but requires a reduction drive to power the rear wheel. Maxwell's documentation highlights practical choices in battery, controller, and motor selection, and the importance of adapting components for new applications.

Hakan Zayin's Electric Go Cart (2019): Hakan's project began with several ideas, including an electric longboard and an arcade machine, but he ultimately chose to build a fully electric go-kart with unique features. Instead of traditional mechanical steering, he implemented an electronic tank-like steering system using a potentiometer, where turning the wheel adjusts the speed of each motor for directional control. The frame was designed in Fusion 360 and made from lightweight plywood, reinforced with jute and epoxy after testing alternatives to carbon fiber for strength and cost-effectiveness. Hakan used CNC milling for fabrication and designed custom pedals with a flex sensor for throttle control, protected in a 3D-printed flexible PLA case. He also integrated an RFID-based authentication system and a display for user feedback, connecting multiple boards via I2C. This project demonstrates the value of iterative prototyping, combining digital fabrication, embedded electronics, and creative problem-solving to achieve a robust, innovative electric vehicle.

TARIGOPPULA SAI ADITYA VYNATHEYA's Self-Balancing Electric Skateboard (2018): This project aimed to create a self-balancing skateboard, I assumed as I read that it will be using using the MPU6050 gyroscope and accelerometer sensor, along with PID control for balancing and maybe a flywheel or some clever motor controll. Although the project appears to have never been fully completed, the design and approach—especially the use of real-time sensor data and control algorithms for stability—were particularly interesting to me and inspired my own ideas for implementing balance and control in personal electric vehicles.


Concept & Design - What I designed.

Project Overview

The Electric Scooter was designed to be strong, portable, and easy to fabricate using digital tools available in a Fab Lab. The main frame is constructed from mild steel square tubing for durability and rigidity, while the overall concept focuses on urban mobility, sustainability, and modularity for easy repair and upgrades.

Design Goals

  • Strong mild steel frame for durability and support
  • Efficient BLDC motor and battery system
  • Simple, robust electronics for control and monitoring
  • Safety features (brakes, lights, dashboard)
  • Modular design for easy assembly and maintenance

Concept Sketches

Initial concept sketch
Initial hand-drawn concept sketch
Refined concept sketch
Refined sketch with component layout

CAD Models

CAD assembly
Full assembly in SolidWorks
Exploded CAD view
Exploded view showing modular parts

The CAD models were created in SolidWorks, allowing for precise dimensioning and easy iteration. The design files are available in the appendix for download.

Credit: The 8-inch wheel hub motor model was based on the design by John Frank, sourced from GrabCAD: hoverboard-wheel-8-bldc-motor-1. The wheels, controller, and batteries are the only parts I purchased; all other components were custom designed and fabricated.

System Architecture

System architecture diagram
  • Power: 36V Li-ion battery pack
  • Motor: 350W BLDC hub motor
  • Controller: Commercial BLDC controller (to be replaced with custom PCB in future)
  • Dashboard: Microcontroller-based display for speed and battery
  • Safety: Mechanical and electronic brakes, LED lights

Design Iterations & Choices

  • Frame Material: Considered aluminum, steel, and plywood. Chose aluminum for weight and strength.
  • Motor Placement: Tested both hub and chain drive. Hub motor chosen for simplicity and fewer moving parts.
  • Electronics: Started with off-the-shelf controller for reliability, with plans for custom PCB as skills improve.
  • Dashboard: Iterated between analog and digital displays; settled on microcontroller-based LCD for flexibility.
  • Braking: Explored regenerative braking, but initial version uses mechanical brakes for safety and simplicity.

Alternative Ideas Considered

  • Self-balancing system using gyroscope and PID control (deferred for future work)
  • Foldable frame for ultra-portability (not implemented in first version)
  • Solar charging integration (planned as a future upgrade)

Sources and Inspirations - The sources I used.

Sources

Inspirations

Xiaomi M365 Electric Scooter
Xiaomi M365

Popular commercial electric scooter known for its minimalist design and portability.

Segway Ninebot Scooter
Segway Ninebot

Reference for dashboard integration and safety features.

DIY Electric Scooter
DIY Community Builds

Various open-source and maker projects for custom scooter fabrication.


Materials and Components - Materials, cost and source.

Overview of Materials and Components

The electric scooter project required a combination of electronic and structural materials, each chosen for their specific roles in the system. Below is an explanation of the main components and materials used, and their functions in the project.

Electronics

  • Copper Clad Board
    Copper Clad Board: Used as the base material for the custom PCB. The copper layer is milled away to create isolated traces for circuit connections.
  • Xiao ESP32-C3 Microcontroller
    Xiao ESP32-C3 Microcontroller: Acts as the brain of the scooter, handling communication with the motor driver, processing sensor data, and controlling outputs.
  • Decoupling Capacitors
    Decoupling Capacitors: Placed near ICs to filter out voltage spikes and noise, ensuring stable operation of sensitive components.
  • Resistors
    Resistors: Used for pull-up, pull-down, termination, and biasing tasks in the circuit. They help set logic levels, stabilize signals, and ensure proper communication on the RS485 bus.
  • Screw Terminals
    Screw Terminals: Provide secure, removable connections for high-current lines such as the battery pack and motor driver, making assembly and maintenance easier.
  • MPU6050
    MPU6050 (Gyroscope & Accelerometer): Allows the microcontroller to sense the scooter's motion and orientation, enabling features like speed monitoring or future balance control.
  • Solder Wire/Paste
    Solder Wire/Paste: Used to assemble and connect electronic components to the PCB.
  • Pin Headers
    Pin Headers: Facilitate modular connections between the PCB and external modules or sensors, making debugging and upgrades more convenient.

Structural Materials

  • Mild Steel Square Tube
    Mild Steel Square Tube: Forms the main frame of the scooter, providing strength and rigidity to support the rider and all components.
  • PLA Filament
    PLA Filament: Used in 3D printing to create custom enclosures for the circuit board and battery, as well as aesthetic covers and mounting brackets. PLA is lightweight and easy to print, making it ideal for prototyping and protective housings.

Each of these materials and components plays a crucial role in the overall function and reliability of the electric scooter. The electronics ensure precise control and monitoring, while the structural materials provide a safe and durable platform for urban mobility.

Materials - Bill of Materials

Qty Description Unit Price Link

Parts and Systems Fabricated - What parts and systems were made?

Overview of Fabricated Parts and Systems

The fabrication of my electric scooter involved making several custom parts and systems using a combination of digital fabrication, electronics assembly, and traditional workshop techniques. Below I detail the main components I fabricated: the custom circuit board, the scooter frame, and various 3D printed enclosures and holders.

Custom Circuit Board for Motor Driver Interface

To control the BLDC motor driver and interface it with my microcontroller, I designed and fabricated a custom PCB. The board includes logic-level shifting, signal conditioning, and connectors for throttle input, brake sensors, and dashboard communication.

  • Design: The schematic and PCB layout were created in KiCad. I included headers for easy connection to the motor driver, microcontroller, and sensors.


    RS485 Transciever:

    RS485 is a differential serial communication standard widely used for robust, long-distance, and noise-resistant data transmission in industrial and embedded systems. In my circuit, the MAX485 chip implements the RS485 protocol, acting as a bridge between the microcontroller (UART) and the motor driver, allowing reliable data exchange even in electrically noisy environments.

    Custom PCB fabrication

    The section of the circuit showing how the RS485 tranciever is implemented.

    • A/B Lines: The A and B lines are the differential data lines. A 1kΩ (R5) pull-up on A and a 1kΩ (R6) pull-down on B bias the bus to a known idle state when no device is driving it. The 120Ω (R8) resistor between A and B provides proper termination, matching the characteristic impedance of the cable to minimize signal reflections.
    • RE/DE Pins: RE (Receiver Enable) and DE (Driver Enable) are tied together and pulled up with a 10kΩ (R3) resistor. This configuration ensures the MAX485 is enabled for transmitting data when the microcontroller sets the line high, and disables the driver when low, allowing for half-duplex communication.
    • RO/DI Pins: RO (Receiver Output) connects to the UART RX pin and has a 10kΩ (R2) pull-up to ensure a defined logic level when not driven. DI (Driver Input) connects to UART TX with a 10kΩ (R1) pull-down, preventing floating input when idle.

    This setup enables the microcontroller to send and receive UART data over the RS485 bus, providing noise immunity and supporting longer cable runs between the control board and the motor driver.


    Current and Voltage Sensing:

    The circuit includes a current sensing IC ACS712 and voltage divider (R9, R10) to monitor the battery current and voltage. This data is fed back to the microcontroller for real-time monitoring and control.

    To protect the analog input of the microcontroller from voltage spikes or accidental overvoltage from the voltage sensing circuit, a BZX85 3.0V Zener diode and a 1kΩ resistor (R11) are used. The resistor R11 limits the current into the analog pin, while the Zener diode clamps the voltage to a safe maximum (about 3V), preventing the analog pin from being exposed to voltages higher than the microcontroller can tolerate.

    Current and voltage sensing

    The section of the circuit showing how the current and voltage sensing is implemented.


    Connections to the microcontroller (Xiao esp32 c3):

    Current and voltage sensing

    The section of the circuit showing how the current and voltage sensing is implemented.


    The complete circuit design

    Current and voltage sensing

    The section of the circuit showing how the current and voltage sensing is implemented.


    PCB Layout: The PCB layout was designed in KiCad, ensuring proper trace widths for current handling and clearances for high-voltage components.

    PCB layout

    PCB layout showing component placement and routing.

    PCB layout

    PCB layout shown on cad model.


  • Fabrication: The PCB was milled using the Fab Lab's desktop PCB milling machine. After milling, I soldered all components by hand, including pin headers, resistors, capacitors, and connectors.

    PCB Fabrication Process (Bungard CCD)

    1. Generate Manufacturing Files in KiCad:
      • Opened the completed PCB design in KiCad.
      • Generated Gerber files for the top copper layer (since the board is single-sided).
      • Generated drill files for all plated and non-plated holes.
      KiCad Gerber export

      Exporting Gerber and drill files in KiCad.

    2. Import Gerber and Drill Files into CopperCAM:
      • Launched CopperCAM and imported the Gerber (top layer) and drill files.
      • Defined the board outline and verified alignment of all layers.
      • Configured tool settings for isolation milling, drilling, and cutout operations.
      CopperCAM import

      Gerber and drill files loaded in CopperCAM.

      After importing, I made sure the tool selection for each operation was correct. For both engraving and hatching, I used a 30-degree V-bit with a tip diameter of 0.1 mm. For cutting and drilling, a 1 mm diameter flute cutter was used. Larger diameter holes would be made through a boring operation at a speed of 5 mm/s.

      CopperCAM tool settings

      Tool selection and operation settings in CopperCAM.

    3. Generate HPGL Toolpaths in CopperCAM:
      • Used CopperCAM to generate toolpaths for copper isolation, drilling, and board cutout.
        CopperCAM contour calculation settings

        CopperCAM settings used for calculating board contours before generating toolpaths.

        I also generated hatches to remove all unnecessary copper from the PCB. This is important because leaving large areas of unused copper can lead to unwanted electrical connections (shorts), increased risk of corrosion, and can interfere with high-frequency signals due to stray capacitance. Hatching is a process where the software fills unused copper areas with a pattern of parallel lines (hatch lines) instead of leaving them as solid copper. These hatch areas help maintain mechanical stability of the board, reduce copper waste, and minimize the risk of warping during soldering by balancing copper distribution.

        CopperCAM hatch generation

        Settings used for generating hatches.

        Preview of Final Circuit:

        After completing all the necessary calculations and adjustments in the PCB design software, the final circuit preview provides a realistic visualization of how the board will look once fabricated. This includes the placement of all components, traces, drills, and labels, ensuring that the design is ready for manufacturing and assembly.

        Final circuit preview

        Preview of the completed circuit as rendered in CopperCAM before fabrication.

      • Exported the toolpaths as HPGL files, the only format supported by RoutePro 3000.
      CopperCAM toolpath output data settings

      Setting the outptut data format and settings

      To have more control over the PCB fabrication process, I generated separate HPGL files for each operation: cutout, hatches, drills, and traces. This approach allowed me to fine-tune the milling parameters for each step, such as adjusting spindle speed, depth, and tool selection according to the specific requirements of each process. By isolating these operations, I could also pause between steps to inspect the board, change tools, or make adjustments, ensuring higher precision and reducing the risk of errors during manufacturing.

      CopperCAM toolpath output data settings

      This is where each layer toolpath was exported to separate files, the engarving layer for traces, hatches layer for removing unwanted copper, drills and cutout.

    4. Load HPGL Files into RoutePro 3000:
      • Opened RoutePro 3000, the Bungard CCD control software.
      • Imported the HPGL files for milling, drilling, and cutout.
      • Secured the copper-clad board on the Bungard CCD bed and set the origin.
      RoutePro 3000 setup

      HPGL files loaded and ready for milling in RoutePro 3000.

    5. Milling the PCB on Bungard CCD:
      • Started the milling process, beginning with copper isolation, then drilling, and finally board cutout.
      • Monitored the process to ensure correct tool changes and alignment.

      Video: Bungard CCD milling the PCB.

      Milled PCB

      PCB after milling and drilling, ready for soldering.

    6. Soldering Components:
      • Cleaned the milled PCB and gathered all components.
      • Soldered components by hand, starting with the smallest (resistors, ICs) and finishing with connectors and headers.
      • Inspected all joints and cleaned flux residue.

      Video: Soldering components onto the fabricated PCB.

      Assembled PCB

      Assembled and soldered PCB.


  • Testing: After assembling the board, I first checked for shorts between power and ground using a multimeter to ensure there were no soldering errors. Once confirmed, I plugged in the circuit and powered it up to verify that everything worked as expected. I then tested the board in-circuit with the motor driver to ensure reliable signal transmission.

    Video: Testing the assembled PCB in operation with the motor driver.

    To test the motor driver and verify reliable communication, I wrote Arduino code that fully implements the Modbus RTU protocol over RS485. This allowed the microcontroller to send commands and receive status data from the motor driver, ensuring that the custom PCB and RS485 transceiver worked as intended.

    Below is a simplified example of the Arduino code used for Modbus communication with the motor driver:

    
    /*
      * This code will be the back bone of my electric scooter project.
      * It will be used to control the motor driver using Modbus RTU over RS485.
      * This code demonstrates how to communicate with a motor driver using Modbus RTU over RS485.
      * It uses the ModbusMaster library to handle Modbus RTU framing and CRC.
      * The MAX485 transceiver is used to convert UART signals to RS485 differential signals.
      * The code initializes the Modbus communication, sends commands to the motor driver.
    */
    
    #include <ModbusMaster.h>
    #include <Wire.h>
    
    // Constants for MAX485 RS485 Transceiver
    // The pins bellow are the physicall pins of the Max 485 transceiver connected to the ESP32-C3 Xiao,
    // they controll the direction of the data flow in the RS485 bus.
    #define MAX485_DE           D9
    #define MAX485_RE_NEG       D8
    
    
    // Next i define the Modbus registers for the motor driver
    // These registers are used to control the motor driver and read its status.
    // The addresses are based on the motor driver's Modbus documentation.
    // The addresses are in hexadecimal format.
    // these are enough to get the motor driver working in velocity mode, 
    // which is the mode I will be using for my scooter.
    
    /*
      * Keep in mind that these are the addresses I need to keep adjusting in order 
      * to controll the behaviour of the motor driver and it wheels.
      */
      #define CONTROL_WORD 0x200E
      #define CONTROL_MODE 0x200D
    
      #define ACCELERATION_TIME_LEFT    0x2080
      #define ACCELERATION_TIME_RIGHT   0x2081
      #define DECELERATION_TIME_LEFT    0x2082 
      #define DECELERATION_TIME_RIGHT   0x2083
    
      #define TARGET_VELOCITY_LEFT      0x2088
      #define TARGET_VELOCITY_RIGHT     0x2089
      #define ACTUAL_VELOCITY_LEFT      0x20AB
      #define ACTUAL_VELOCITY_RIGHT     0x20AC
    
      // Settings for the motor driver (CONTROL_WORD values)
      const uint16_t QUICK_STOP   = 0x05;
      const uint16_t CLEAR_FAULT  = 0x06;
      const uint16_t STOP         = 0x07;
      const uint16_t ENABLE       = 0x08;
    
      // needed in position control
      const uint16_t START        = 0x10;  // SYNC
      const uint16_t START_LEFT   = 0x11;  // ASYNC
      const uint16_t START_RIGHT  = 0x12;  // ASYNC
    
    const uint16_t ACCELERATION_DECELERATION_TIME = 1000; // In milliseconds(ms)
    
    // ModbusMaster object instantiation
    ModbusMaster node;
    
    /*  
      * IMPORTANT FNCTION PROTOTYPES
      * The function preTransmission() is called before each Modbus transmission.
      * It sets the MAX485 transceiver to transmit mode by setting the DE and RE_NEG pins high.
      * The function postTransmission() is called after each Modbus transmission.
      * It sets the MAX485 transceiver back to receive mode by setting the DE and RE_NEG pins low.
      * This is necessary to switch the transceiver between transmit and receive modes.
      * The writeRegister() function is a helper function that writes a value to a specific Modbus register.
      * It uses the ModbusMaster library to send a write request to the motor driver.
      * The syncronousMove() function sends a command to the motor driver to set the 
      * target velocities for both wheels.
      * It uses the ModbusMaster library to write multiple registers at once.
      */
      void preTransmission();
      void postTransmission();
      bool writeRegister(uint16_t address, uint16_t value);
      bool syncronousMove(int leftVelocity, int rightVelocity);
    
    
    
    void setup() {
        pinMode(MAX485_RE_NEG, OUTPUT);
        pinMode(MAX485_DE, OUTPUT);
    
        // Init in receive mode
        digitalWrite(MAX485_RE_NEG, 0);
        digitalWrite(MAX485_DE, 0);
    
        Serial.begin(115200);
        Serial1.begin(115200, SERIAL_8N1, 20, 21);
    
        // Modbus slave ID 1
        node.begin(1, Serial1);
    
    
        // Callbacks allow us to configure the RS485 transceiver correctly
        node.preTransmission(preTransmission);
        node.postTransmission(postTransmission);
    
        // System start with default as Velocity mode
        Serial.println("connecting to servo driver...");
    
    
        /*      
          * The following code is used to initialize the motor driver and set it to velocity mode.
          * It sends a series of Modbus commands to the motor driver to clear faults, enable it, 
          * set the control mode to velocity, and configure acceleration and deceleration times.
          * The values used in the commands are based on the motor driver's Modbus documentation.
          * The code will keep trying to connect to the motor driver until it succeeds.
          */
          while(true){
            // start by handshaking with the motor driver
            if (writeRegister(CONTROL_WORD, CLEAR_FAULT) &&
                writeRegister(CONTROL_WORD, ENABLE) &&
                writeRegister(CONTROL_MODE, 0x03) &&
                writeRegister(ACCELERATION_TIME_LEFT, ACCELERATION_DECELERATION_TIME) &&
                writeRegister(ACCELERATION_TIME_RIGHT, ACCELERATION_DECELERATION_TIME) &&
                writeRegister(DECELERATION_TIME_LEFT, ACCELERATION_DECELERATION_TIME) &&
                writeRegister(DECELERATION_TIME_RIGHT, ACCELERATION_DECELERATION_TIME)) {
    
              Serial.println("Motor driver is Listening...");
              //isMotorDriverOnline = true;
              delay(500);
              break;
            }else{
              Serial.println("Reaching out to Motor driver...");
            }
            delay(1000);
          }
    
      
    }
    
    /*
      * The loop function is where the main logic of the program runs.
      * In this case, it sends a command to the motor driver to move both wheels at a speed of 350.
      * The syncronousMove() function is called to send the command.
      * The function returns true if the command was sent successfully, and false otherwise.
      * The loop will keep running, sending the same command every second.
      */
    void loop() {
      bool moveSent = syncronousMove(350, 350);
      Serial.println("Speeding...");
      delay(1000);
    }
    
    // Callback function before transmission
    void preTransmission() {
        digitalWrite(MAX485_RE_NEG, 1);
        digitalWrite(MAX485_DE, 1);
    }
    
    // Callback function after transmission
    void postTransmission() {
        digitalWrite(MAX485_RE_NEG, 0);
        digitalWrite(MAX485_DE, 0);
    }
    
    /**
      * This function writes a value to a specific Modbus register.
      * It uses the ModbusMaster library to send a write request to the motor driver.
      * The function returns true if the write operation was successful, and false otherwise.
      */
      bool writeRegister(uint16_t address, uint16_t value) {
        return node.writeSingleRegister(address, value) == 0;
      }
    
    /**
      * This function sends a command to the motor driver to set the target velocities for both wheels.
      * It uses the ModbusMaster library to write multiple registers at once.
      * The left velocity is written to register 0x2088 and the right velocity to register 0x2089.
      * The function returns true if the command was sent successfully, and false otherwise.
      */
      bool syncronousMove(int leftVelocity, int rightVelocity){
        node.setTransmitBuffer(0, -leftVelocity);
        node.setTransmitBuffer(1, rightVelocity);
    
        return node.writeMultipleRegisters(0x2088, 2);
      }
    
                    

    This code uses the ModbusMaster library to handle Modbus RTU framing and CRC, and toggles the MAX485 transceiver between transmit and receive modes. It demonstrates reading a register from the motor driver and printing the result, confirming that the RS485 and Modbus implementation is functional.


Scooter Structure (Mild Steel Square Tube)

The main frame of the scooter was fabricated from mild steel square tubing for strength and durability. The design was optimized for weight and ease of assembly.

  • Design: The frame was modeled in SolidWorks, with precise dimensions for all joints and mounting points.

    CAD Design Process in SolidWorks

    The scooter frame was modeled in SolidWorks using the Weldments feature, which is ideal for designing metal structures that will be welded together. Weldments allow you to create complex frames from standard profiles, manage cut lists automatically, and visualize joints and intersections, making it much easier to plan fabrication and assembly for welded metal projects.

    SolidWorks Weldments Setup
    Setting up Weldments before sketching the structure in a single part file
    SolidWorks Complete Structure
    Complete scooter frame structure modeled in SolidWorks
    SolidWorks Structure with Wheels Assembly
    Frame with wheels placed in assembly for scale and proportions review

    Using Weldments in SolidWorks enabled efficient design, accurate cut lists, and clear visualization of the welded joints, ensuring the frame could be fabricated precisely as modeled.

  • Cutting: I used a metal chop saw to cut the steel tubes to length according to the CAD drawings.

    Cutting Process

    Cutting small steel pieces with a hand hacksaw.
    Cutting long steel tubes with mechanical hacksaw
    Cutting longer steel tubes using a mechanical hacksaw for accuracy and speed.
    First wheel attachment assembly
    First wheel attachment assembly on the scooter frame.
  • Welding: The frame sections were welded together using MIG welding. I ensured all joints were square and reinforced high-stress areas.

    Welding Process

    Video: Arc welding the scooter frame sections together.
    Arc welding process
    Photo: Close-up of arc welding on a frame joint.

    Arc welding was used to join the steel tubes, ensuring strong and durable joints for the scooter frame. Proper safety gear and ventilation were used throughout the process.

  • Finishing: I will leave finishing (grinding welds, priming, and painting) until after I have test-fitted all components and completed initial testing. This way, if I need to make adjustments or fixes, I won't risk damaging the final finish.
Scooter frame fabrication

Scooter frame welded from mild steel square tube.

3D Printed Enclosures and Holders

To protect the electronics and secure the battery and motor driver, I designed and 3D printed an enclosure to hold the components firmly.

  • Electronics Enclosure: Designed in Solidworks to fit the custom PCB, with ventilation slots and mounting tabs. Printed in PLA for ease of prototyping.
  • Battery Holder: Custom-fit to the battery pack.
  • Motor Driver Mount: A robust bracket to secure the motor driver to the frame, ensuring good airflow and easy access for wiring.
  • CAD Model of Enclosure: Designed in SolidWorks to precisely fit the custom PCB and connectors.
    CAD model of PCB enclosure
    CAD model of the custom PCB enclosure in SolidWorks
  • The 3d Printing Process: The enclosure was printed on a Ender 3v 3 plus.
    Printing enclosuer
    3D printing
    Enclosure mounted on scooter frame (top view)
    the Cover was too big so it had to be printed in 2 halfs uisng creality print's built in splitter.
  • Enclosure Mounted on Scooter Frame: The finished enclosure was securely attached to the scooter.
    Video: 3D printing the PCB enclosure
  • Right Handle with Throttle Attachment: Designed in SolidWorks to fit the handlebar and securely hold the throttle lever and electronics. The model includes ergonomic shaping for comfort and a mounting slot for the throttle.
    CAD model of right handle with throttle
    CAD model of the right handle with throttle attachment
    3D printed right handle with throttle
    3D printed right handle with throttle attachment
  • Left Handle: Custom designed for symmetry and grip comfort, matching the right handle but without throttle features.
    CAD model of left handle
    CAD model of the left handle
    3D printed left handle
    3D printed left handle
  • Bracket Connecting Handlebar to Steering Column: This bracket was designed for a precise fit between the handlebar and the steering column, ensuring rigidity and alignment. The CAD model features mounting holes and reinforcement ribs.
    CAD model of handlebar-to-steering bracket
    CAD model of the bracket connecting handlebar to steering column
    3D printed handlebar-to-steering bracket
    3D printed bracket for handlebar and steering column

All 3D printed parts were sliced using Creality Print and printed on a Creality Ender 3 V3 plus printer. I iterated the designs based on fit and function, updating the models as needed.

  • Video of the 3D Printing Process:
    Video: 3D printing in progress
  • Handlebar Assembly - Handles and Bracket on Steering Shaft

    Below is an image showing the assembled left and right handles, along with the 3D printed bracket, mounted on the scooter's steering shaft. This demonstrates the final fit and integration of the custom-designed components.

    Handles and bracket assembled on scooter steering shaft
    Handles and bracket assembled on the scooter steering shaft

    Final Assembly

    Below is an image of the fully assembled electric scooter, showcasing the integration of all fabricated parts and systems. For a detailed walkthrough of the system integration process—including wiring, electronics, and final testing—see the System Integration page.

    Fully assembled electric scooter
    The completed electric scooter with all components integrated and mounted.

    Final Program

    Below is the final Arduino program used to control the electric scooter via Modbus RTU over RS485. This code handles communication with the motor driver, processes throttle input, and manages safety features.

    
    // Final Electric Scooter Program
    #include <ModbusMaster.h>
    #include <Wire.h>
    
    
    bool isDebugMode = false;         // Set to true for debugging, false for normal operation
    
    const int throttle = D2;
    const int maxSpeed = 250;
    
    // Calibrated values from testing
    const int joyMin = 0;             // Lowest value when pushed fully down
    const int joyCenter = 2270;       // Value when joystick is at rest
    const int joyMax = 4095;          // Highest value when pushed fully up
    
    const int deadZone = 50;          // Adjust this if needed (20–100 works well)
    
    // Filtering variables
    float filteredThrottle = joyCenter;   // Start at center
    const float filterStrength = 0.2;     // 0.0 (no filter) to 1.0 (heavy filter)
    
    
    // Constants for MAX485 RS485 Transceiver
    #define MAX485_DE           D9
    #define MAX485_RE_NEG       D8
    
    // Velocity control mode registers for the motor driver
    // These registers are used to control the motor driver and read its status.
    #define CONTROL_WORD 0x200E
    #define CONTROL_MODE 0x200D
    
    #define ACCELERATION_TIME_LEFT    0x2080
    #define ACCELERATION_TIME_RIGHT   0x2081
    #define DECELERATION_TIME_LEFT    0x2082 
    #define DECELERATION_TIME_RIGHT   0x2083
    
    #define TARGET_VELOCITY_LEFT      0x2088
    #define TARGET_VELOCITY_RIGHT     0x2089
    #define ACTUAL_VELOCITY_LEFT      0x20AB
    #define ACTUAL_VELOCITY_RIGHT     0x20AC
    
    // Settings for the motor driver (CONTROL_WORD values)
    const uint16_t QUICK_STOP   = 0x05;
    const uint16_t CLEAR_FAULT  = 0x06;
    const uint16_t STOP         = 0x07;
    const uint16_t ENABLE       = 0x08;
    
    // needed in position control
    const uint16_t START        = 0x10;  // SYNC
    const uint16_t START_LEFT   = 0x11;  // ASYNC
    const uint16_t START_RIGHT  = 0x12;  // ASYNC
    
    const uint16_t ACCELERATION_DECELERATION_TIME = 0; // In milliseconds(ms)
    
    // ModbusMaster object instantiation
    ModbusMaster node;
    
    // Function prototypes
    // Callback function before transmission
    // Callback function after transmission
    void preTransmission();
    void postTransmission();
    
    // Function to write a single register
    // Function to send synchronous move command to both motors
    bool writeRegister(uint16_t address, uint16_t value);
    bool syncronousMove(int leftVelocity, int rightVelocity);
    
    
    
    int16_t leftRPM = 0;
    int16_t rightRPM = 0;
    int16_t actualVelocityData[2];
    
    
    bool motorIdle = false;
    float currentCommandedSpeed = 0;
    float rampRate = 5; // RPM per loop iteration (tune this value)
    
    
    void setup() {
        pinMode(throttle, INPUT);
        pinMode(MAX485_RE_NEG, OUTPUT);
        pinMode(MAX485_DE, OUTPUT);
    
            // Init in receive mode
        digitalWrite(MAX485_RE_NEG, 0);
        digitalWrite(MAX485_DE, 0);
    
        Serial.begin(115200);
        Serial1.begin(115200, SERIAL_8N1, 20, 21);
    
        // Modbus slave ID 1
        node.begin(1, Serial1);
        // Callbacks allow us to configure the RS485 transceiver correctly
        node.preTransmission(preTransmission);
        node.postTransmission(postTransmission);
    
           // System start with default as Velocity mode
        Serial.println("connecting to servo driver...");
    
       while(true){
        // start by handshaking with the motor driver
        if (writeRegister(CONTROL_WORD, CLEAR_FAULT) &&
            writeRegister(CONTROL_WORD, STOP) &&
            writeRegister(CONTROL_MODE, 0x03) &&
            writeRegister(ACCELERATION_TIME_LEFT, ACCELERATION_DECELERATION_TIME) &&
            writeRegister(ACCELERATION_TIME_RIGHT, ACCELERATION_DECELERATION_TIME) &&
            writeRegister(DECELERATION_TIME_LEFT, ACCELERATION_DECELERATION_TIME) &&
            writeRegister(DECELERATION_TIME_RIGHT, ACCELERATION_DECELERATION_TIME)) {
    
          Serial.println("Motor driver is Listening...");
          motorIdle = true;
          //isMotorDriverOnline = true;
          delay(500);
          break;
        }else{
          Serial.println("Reaching out to Motor driver...");
        }
        delay(1000);
      }
    
      
    }
    
    
    /*
      * The loop function is where the main logic of the program runs.
      * In this case, it reads the throttle input, processes it, and sends commands to the motor driver.
      * The RideMode() function handles the main control logic for the scooter.
      */
    void loop() {
    
      if(isDebugMode){
        debugMode();
      }else{
        RideMode();
      }
    
      delay(50); // Tighter loop for smoother response
    }
    
    
    /*  * RideMode() function handles the main control logic for the scooter.
      * It reads the throttle input, applies filtering, maps it to RPM, and sends commands to the motor driver.
      * It also manages idle state and smooth ramping of speed.
      */
    void RideMode() {
      int rawThrottle = analogRead(throttle);
    
      // Exponential smoothing
      filteredThrottle = (filterStrength * rawThrottle) + ((1.0 - filterStrength) * filteredThrottle);
    
      int adjustedThrottle = 0;
    
      // Joystick to RPM mapping
      if (filteredThrottle > joyCenter + deadZone) {
        adjustedThrottle = map(filteredThrottle, joyCenter + deadZone, joyMax, 0, -maxSpeed);
      } else if (filteredThrottle < joyCenter - deadZone) {
        adjustedThrottle = map(filteredThrottle, joyMin, joyCenter - deadZone, maxSpeed, 0);
      } else {
        adjustedThrottle = 0;
      }
    
      // Constrain the adjusted throttle to the maximum speed
      adjustedThrottle = constrain(adjustedThrottle, -maxSpeed, maxSpeed);
    
      if(adjustedThrottle < 0){
        adjustedThrottle = 0;
      }
    
      // Read actual velocity from motors
      getActualVelocityLR(); // Updates leftRPM and rightRPM
      float averageSpeed = (leftRPM + rightRPM) / 2.0;
    
      // === Idle logic ===
      if (adjustedThrottle == 0 && abs(averageSpeed) > 0) {
        if (!motorIdle) {
          writeRegister(CONTROL_WORD, 0x07); // Disable motors
          motorIdle = true;
        }
      } 
      else {
        // Re-engage motor
        if (motorIdle && abs(adjustedThrottle) > 50) {
          currentCommandedSpeed = averageSpeed; // Preload current speed
    
          // Write speed to initial velocity register
          writeInitialVelocityBoth(currentCommandedSpeed, currentCommandedSpeed);
          syncronousMove(currentCommandedSpeed, currentCommandedSpeed);
          delay(30); // Optional buffer delay
    
          // Re-enable motors
          writeRegister(CONTROL_WORD, 0x08);
          motorIdle = false;
        }
    
        // Smooth ramping
        if (!motorIdle) {
          if (abs(adjustedThrottle - currentCommandedSpeed) > rampRate) {
            if (adjustedThrottle > currentCommandedSpeed) {
              currentCommandedSpeed += rampRate;
              //delay(100);
            } else {
              currentCommandedSpeed -= rampRate;
              //delay(100);
            }
          } else {
            currentCommandedSpeed = adjustedThrottle;
          }
    
          // Send speed to both motors
          syncronousMove(currentCommandedSpeed, currentCommandedSpeed);
        }
      }
    
      // Debug print
      Serial.print("Raw: ");
      Serial.print(rawThrottle);
      Serial.print(" | Filtered: ");
      Serial.print(filteredThrottle);
      Serial.print(" | TargetSpeed: ");
      Serial.print(adjustedThrottle);
      Serial.print(" | AvgRPM: ");
      Serial.print(averageSpeed);
      Serial.print(" | CmdSpeed: ");
      Serial.print(currentCommandedSpeed);
      Serial.print(" | Idle: ");
      Serial.println(motorIdle);
    }
    
    
    /*  * debugMode() function is used for debugging purposes.
      * It reads a specific register from the motor driver and prints its value.
      * This can help verify communication and register values during development.
      */
    void debugMode(){
      uint16_t result = node.readHoldingRegisters(0x2043, 1);
    
      if (result == node.ku8MBSuccess) {
        uint16_t value = node.getResponseBuffer(0);
        Serial.print("Register value: ");
        Serial.println(value);
      } else {
        Serial.print("Error reading register: ");
        Serial.println(result);
      }
    
      delay(500);
    }
    
    // ----------------------------------------
    // Helper to write initial velocity register
    void writeInitialVelocityBoth(int leftVelocityRPM, int rightVelocityRPM) {
      // Adjust scaling if your motor expects 0.1 RPM (multiply by 10)
      int leftScaled = leftVelocityRPM;
      int rightScaled = rightVelocityRPM;
    
      uint8_t resultLeft = node.writeSingleRegister(0x2043, leftScaled);
      if (resultLeft != node.ku8MBSuccess) {
        Serial.print("Left motor init velocity write error: ");
        Serial.println(resultLeft);
      }
    
      uint8_t resultRight = node.writeSingleRegister(0x2073, rightScaled);
      if (resultRight != node.ku8MBSuccess) {
        Serial.print("Right motor init velocity write error: ");
        Serial.println(resultRight);
      }
    }
    
    /*  * getActualVelocityLR() function reads the actual velocity of both motors.
      * It uses Modbus to read the holding registers for left and right motor velocities.
      * The velocities are stored in the actualVelocityData array and converted to RPM.
      * The left motor's RPM is negated to match the expected direction.
      */
    void getActualVelocityLR(){
      int size = 2;
      uint8_t j, result;
      result = node.readHoldingRegisters(0x20AB, 2);
    
      if(result == node.ku8MBSuccess){
        for (j = 0; j<size; j++){
          uint16_t mVal = node.getResponseBuffer(j);
          actualVelocityData[j] = *((int16_t*)&mVal);
          actualVelocityData[j] = actualVelocityData[j]*0.1;
        }
    
        leftRPM = -actualVelocityData[0];
        rightRPM = actualVelocityData[1];
      }
    }
    
    
    // Callback function before transmission
    void preTransmission() {
        digitalWrite(MAX485_RE_NEG, 1);
        digitalWrite(MAX485_DE, 1);
    }
    
    // Callback function after transmission
    void postTransmission() {
        digitalWrite(MAX485_RE_NEG, 0);
        digitalWrite(MAX485_DE, 0);
    }
    
    bool writeRegister(uint16_t address, uint16_t value) {
      return node.writeSingleRegister(address, value) == 0;
    }
    
    
    
    bool syncronousMove(int leftVelocity, int rightVelocity){
      node.setTransmitBuffer(0, -leftVelocity);
      node.setTransmitBuffer(1, rightVelocity);
    
      return node.writeMultipleRegisters(0x2088, 2);
    }
    
                

    Processes Used - What processes were used?

    • 3D Printing:

      I used 3D printing to fabricate custom enclosures, handlebar grips, brackets, and holders for the electronics and battery. This process allowed for rapid prototyping and precise fitting of parts. Models were designed in CAD software and printed using PLA filament on a Creality Ender 3 V3 Plus printer.

    • PCB Isolation Milling:

      The custom circuit board was produced using PCB isolation milling. I designed the PCB in KiCad, generated toolpaths in CopperCAM, and milled the board on a Bungard CCD machine. This process removes unwanted copper from a copper-clad board, creating isolated traces for the circuit without using chemical etching.

    • Metal Fabrication:

      The scooter frame was built from mild steel square tubing. I cut the tubes to size using a hacksaw and mechanical saw, then welded the pieces together using MIG welding. This provided a strong and durable structure for the scooter.

    • Arc Welding:

      Arc welding was used to join steel components of the scooter frame. This process involved using an electric arc to melt and fuse the metal at the joints, ensuring strong and permanent connections for structural integrity.

    • Component Soldering:

      After milling the PCB, I soldered all electronic components by hand, including microcontrollers, connectors, resistors, and capacitors. Careful soldering ensured reliable electrical connections and robust operation of the control system.


    Questions and Challenges - What worked? What didn’t?

    What Worked and What Didn’t

    The development process involved several iterations, especially with the custom PCB for the motor driver interface. I fabricated two PCBs that did not work before succeeding on the third attempt.

    First PCB: Incorrect Logic Levels

    In my first PCB design, I powered several ICs (such as the MAX485 and ACS712 current sensor IC) with 5V, while the Xiao ESP32-C3 microcontroller operates at 3.3V logic. This caused the outputs from these ICs to exceed the safe input voltage for the ESP32-C3’s pins.

    Why is this bad?
    The ESP32-C3’s GPIO pins are not 5V tolerant. Applying voltages above 3.3V can damage the microcontroller, cause unreliable operation, or even permanently destroy the chip.

    However, the ACS712 current sensor IC required a 5V supply. To safely read its analog output with the ESP32-C3, I used a voltage divider to reduce the sensor’s output voltage to a safe level for the microcontroller’s ADC input.

    Voltage divider solution for current sensor
    Voltage divider circuit used to safely interface the 5V current sensor output with the 3.3V ADC input.
    First failed PCB
    First PCB attempt – logic level mismatch caused failure.

    Second PCB: Swapped RX/TX Lines

    After correcting the logic level issue and building a second PCB, I encountered another problem: the MAX485 transceiver was not communicating. Upon inspection, I realized I had swapped the RX and TX lines between the microcontroller and the MAX485, preventing any data transmission.

    To confirm that the issue was indeed swapped RX and TX lines, I hard-soldered an external RS485 module directly to the board and connected it to the controller. This time, I received a response from the motor driver, proving that the communication lines were the problem. With this confirmation, I went back to KiCad and corrected the RX/TX routing in my PCB design before fabricating the next revision.

    Second failed PCB
    Second PCB attempt – RX and TX lines were swapped, so RS485 communication failed.

    Final Working PCB

    On the third attempt, I corrected both the logic level and RX/TX wiring issues. The PCB worked as intended, allowing reliable communication between the ESP32-C3 and the motor driver.

    Final working PCB
    Final working PCB – correct logic levels and wiring.

    These challenges taught me the importance of carefully checking voltage compatibility and signal routing in mixed-voltage systems, as well as the value of iterative prototyping and thorough testing.


    Evaluation - How was it evaluated?

    Evaluation Criteria

    • Ride Performance: The scooter was tested with riders up to 90 kg. It successfully supported the weight and maintained stable operation on flat and slightly inclined surfaces.
    • Range: The scooter achieved a tested range of 6 km on a single charge. While this is modest, it is a solid result for a first prototype and provides a baseline for future improvements.
      Video: Range test ride covering 6 km on a single charge.
    • Safety Features:It uses electric braking: as soon as you release the throttle, the motor controller automatically applies gradual electric braking to slow the scooter down. At a speed of 2.5 meters per second, this braking was observed to be safe and effective for normal use. All welds and joints were inspected after testing, and no structural failures occurred during normal operation.
    • User Interface Usability: A digital dashboard and advanced user interface are planned for future versions. For this prototype, basic controls and indicators were used, and the system was easy to operate for test riders.

    Overall, the project met its core goals for rideability, safety, and basic range. Future work will focus on extending range, refining the user interface, and adding more advanced features.


    Implications and Reflection - What are the implications?

    Implications of the Electric Scooter Project

    The development of my electric scooter project demonstrates the potential for sustainable, accessible, and locally manufactured personal transportation. By designing and fabricating the scooter structure myself, I was able to tailor the frame for lightweight portability and urban use. The use of BLDC motors, paired with a commercial controller, allowed for efficient power delivery and reliable performance.

    A key implication of this project is its contribution to sustainable transport solutions. Electric scooters offer a clean alternative to fossil-fuel vehicles for short commutes, helping to reduce urban congestion and air pollution. By documenting the fabrication process and sharing design files, I hope to encourage local manufacturing and empower others to build or adapt similar vehicles using accessible digital fabrication tools.

    Reflections and Future Improvements

    Building a custom interface to control the BLDC motor controller was both challenging and rewarding. It provided hands-on experience with embedded systems and user interface design. In future iterations, I would focus on improving the integration between the controller and the dashboard, possibly adding wireless connectivity for remote monitoring and diagnostics. I would also explore optimizing the frame design for even greater durability and ease of assembly.

    One significant area for improvement is implementing regenerative braking, which I have not yet achieved. Adding this feature in the future could further increase energy efficiency. Additionally, as I gain more experience in electronics design, I plan to build my own BLDC motor controller to replace the commercial unit, allowing for deeper customization and learning. Developing a more advanced battery management system (BMS) would also enhance safety and extend the scooter's lifespan.

    Opportunities for Replication and Collaboration

    All design files, code, and documentation are openly shared to enable others to replicate, modify, or build upon this project. The modular approach to the scooter's electronics and structure makes it adaptable for different needs or environments. I encourage others to experiment with alternative materials, motor types, or control interfaces, and to contribute improvements back to the community. Through collaboration and open-source sharing, projects like this can accelerate the adoption of sustainable transport and digital fabrication skills worldwide.


    Appendix

    Download Project Files

    All files are provided under a Creative Commons Attribution Non Commercial license.


    Acknowledgements - Who helped?

    I would like to acknowledge the Fab Academy for providing this incredible opportunity to learn and grow in digital fabrication.
    Special thanks to my sponsors for their generous support throughout this journey.
    I am also deeply grateful to my instructor for their guidance, encouragement, and expertise.