DIY Smartwatch From Scratch — Custom Firmware, Real Sensors
by hanselkay8 in Circuits > Arduino
2932 Views, 18 Favorites, 0 Comments
DIY Smartwatch From Scratch — Custom Firmware, Real Sensors
Smartwatches you buy off the shelf — Fitbit, Garmin, Apple Watch — all have one thing in common: the firmware is a black box. You can't see which registers are being written, which interrupts are firing, or how raw ADC values turn into a BPM reading on your screen.
This project flips that. We're building a smartwatch from scratch — custom PCB, custom firmware, with full control over how the sensors behave. No unnecessary abstraction where it matters.
What it can do:
- Heart rate (HR) monitoring via MAX30100
- Blood oxygen (SpO2) via MAX30100 IR/Red LED ratio
- Step counting via BMI160 accelerometer with interrupt-driven detection
- All data displayed live on a small screen
Sensors used:
- MAX30100 — optical pulse oximeter and heart rate sensor, driven via library
- BMI160 — 6-axis IMU (accelerometer + gyroscope), driven bare metal over I²C/SPI
The BMI160 is controlled bare metal — we write directly to its register addresses, configure the accelerometer ODR and interrupt engine manually, and parse raw bytes ourselves. Steps 4 and 5 will break down every register in detail.
Project status:
- ✅ Heart rate & SpO2 — done
- ✅ Step counter — done
- ✅ Custom PCB & soldering — done
- 🔲 Smartphone connection (BLE) — planned for future development
Smartphone connectivity hasn't been implemented in this version. All data is currently displayed directly on the watch screen. This is the next target on the roadmap.
Why go deep on the BMI160?
Most DIY wearable projects treat the IMU as a black box — include a library, call a function, get a number. Here we skip that for the BMI160 and talk directly to the hardware. This means we control exactly which interrupt fires on a detected step, how sensitive the detection is, and how to tune it without guessing. That level of control is what makes this more than just a parts-assembly project.
Supplies
Components Used
The complete Bill of Materials (BOM) is shown in the image above.
Below is a numbered breakdown of the components used in this build along with their approximate prices.
1. Main Components
- ESP32-C3 Super Mini (U1)
Main microcontroller with built-in WiFi and BLE connectivity. This chip runs the entire smartwatch firmware and handles sensor communication, UI rendering, and power management.
Price: Rp 30,000 (~$1.90 USD)
- MAX30100 Sensor Module
Optical heart rate and SpO₂ sensor communicating through I²C. It measures blood volume changes using infrared and red light.
Price: Rp 20,000 (~$1.30 USD)
- BMI160 (GY-BMI160 Module)
Accelerometer and gyroscope used for motion detection and step counting. The internal DSP allows step counting without heavy CPU processing.
Price: Rp 46,000 (~$3.00 USD)
- 1.69" TFT Display (ST7789 Driver)
Main display used for rendering the watch interface, graphs, and sensor data. The display communicates via SPI.
Price: Rp 45,000 (~$2.90 USD)
- 400 mAh LiPo Battery
Primary power source for the smartwatch.
Price: Rp 8,000 (~$0.50 USD)
2. Power Management
- LTH7 LiPo Charging IC (U3, C5342381)
Handles safe charging of the LiPo battery from the USB input.
Price: Rp 2,000 (~$0.13 USD)
- ST1340BB Buck Converter (U4)
Converts the input voltage to a stable 3.3V supply for the ESP32 and sensors.
Price: Rp 2,000 (~$0.13 USD)
- IRLML6401 P-Channel MOSFET (Q2)
Used for battery power path switching.
Price: Rp 500 (~$0.03 USD)
- IRLML2502 MOSFET (Q1)
Used to control the display backlight.
Price: Rp 500 (~$0.03 USD)
- 1N5819 Schottky Diodes (D1, D2)
Provide power path protection.
Price: Rp 500 (~$0.03 USD)
3. Passive Components
- 3.3 µH Inductor (L1)
Required by the buck converter to regulate voltage efficiently.
Price: Rp 500 (~$0.03 USD)
- 0603 Resistors (R1–R16)
Used for I²C pull-ups, battery voltage divider, LED resistors, and supporting circuits.
Price: Rp 1,000 (~$0.06 USD total)
- 0603 Capacitors (C1–C10)
Used for power stabilization and noise filtering.
Price: Rp 1,500 (~$0.10 USD total)
4. User Interface
- Push Button (SW1)
Used for user interaction and menu navigation.
Price: Rp 500 (~$0.03 USD)
5. Other Components
- Custom PCB
Manufactured through PCB fabrication services such as JLCPCB or PCBWay.
Typical price per board (after splitting a batch order):
Rp 30,000 (~$1.90 USD)
6. Estimated Total Cost
Total estimated hardware cost:
≈ Rp 188,000
≈ $12 USD
Component Notes
The MAX30100 sensor can be replaced with MAX30102, which is often easier to find. The pinout is very similar, although some register configuration adjustments may be required in the firmware.
The BMI160 module (GY-BMI160) already includes pull-up resistors and decoupling capacitors on the module itself, reducing the number of external components required.
PCB Cost Note
PCB manufacturers such as JLCPCB or PCBWay typically require a minimum order of five boards.
Even if only one board is needed, you will receive multiple boards from the factory. When dividing the total cost across the batch, the effective cost per PCB is usually under Rp 30,000 (~$1.90 USD).
You can keep the extra pcb for future revisions or share them with others bulding the same project.
PCB Design Overview
Step — PCB Files and Important Notes
To make it easier for others to reproduce this project, the PCB design files are provided. The files include the original EasyEDA project, so the design can be opened and edited directly without needing to recreate the schematic or PCB layout from scratch.
Simply import the EasyEDA project file into EasyEDA and the full schematic and PCB layout will load automatically. From there, the design can be modified, exported as Gerber files, or directly sent to a PCB manufacturer.
However, there are a few important notes regarding the current PCB revision.
Known Issue — Display Backlight pin Orientation
In this revision of the PCB, the anode and cathode orientation of the backlight are accidentally swapped in the layout.
Because of this, the diode will not function correctly if soldered according to the silkscreen orientation.
To fix this issue during assembly, the diode needs to be installed with a small jumper wire that corrects the connection between the pads. This modification is simple and does not affect the rest of the circuit.
A photo of the jumper fix will be shown in the assembly section so builders can easily replicate the correction.
EasyEDA Project File
The EasyEDA project file is included so that builders can:
- Open the schematic and PCB layout directly
- Modify the design if needed
- Export Gerber files for manufacturing
- Order the PCB directly from EasyEDA's integrated fabrication services
This makes it much easier to customize the board or create improved revisions.
Deep Sleep Feature Note
If you plan to use the ESP32 deep sleep feature, an additional hardware modification is recommended.
The current PCB revision does not include a dedicated wake button for deep sleep mode. To wake the ESP32 from deep sleep using external input, a push button connected to GPIO 5 should be added.
This button should connect GPIO 5 to ground when pressed, allowing the ESP32 to wake from deep sleep via an external interrupt.
Adding this button enables significantly lower power consumption during standby, which is especially useful for battery-powered wearable devices like this smartwatch.
Before assembling the smartwatch hardware, the circuit first needs to be converted into a PCB design that can be manufactured.
After completing the schematic diagram (as shown in the previous image), the next step is to translate that schematic into a PCB layout. The PCB layout defines the physical placement of components and the copper traces that connect them electrically. This layout will eventually be exported as manufacturing files used by PCB fabrication services.
In this project, the PCB layout was designed using EasyEDA, a web-based electronic design automation tool that allows schematic capture, PCB layout, and direct integration with manufacturing services.
Although EasyEDA was used in this project, many other professional and hobbyist tools can also be used to design PCBs. Some popular options include:
- KiCad (open-source and widely used)
- Altium Designer (professional industry tool)
- Autodesk Eagle
- OrCAD / Cadence
- Proteus
- DipTrace
- CircuitMaker
- DesignSpark PCB
All of these tools follow a similar workflow: create the schematic, assign footprints to components, and then move into PCB layout mode.
Converting the Schematic to a PCB Layout
Once the schematic is complete, each component in the design must be assigned a footprint. A footprint defines the physical copper pads and dimensions that correspond to a specific component package (for example 0603 resistors, SOT-23 MOSFETs, or module headers).
After footprints are assigned, the design can be transferred into the PCB layout editor. The software will place all components on the board area and generate connection lines (nets) indicating which pins need to be connected.
The next step is component placement. This is one of the most important stages in PCB design. Components should be arranged logically based on their function in the circuit.
For example:
- The ESP32-C3 module is placed near the center of the board as the main controller.
- The BMI160 motion sensor and MAX30100 sensor are positioned where they can be easily exposed to the user or environment.
- The power management section (charger IC, buck converter, inductor, and capacitors) is grouped together to keep power routing short and efficient.
- The display connector is placed near the edge of the board for easier cable routing.
Proper component placement helps reduce trace length, improves signal integrity, and simplifies routing.
Routing the PCB Traces
After components are positioned, the next step is routing, which means drawing copper traces to connect all the required signals.
During routing, several engineering considerations should be followed:
Power traces should be wider than signal traces to handle higher current safely.
High-speed signals such as SPI communication lines should be kept as short as possible.
Sensitive communication lines such as I²C (SDA and SCL) should be routed carefully to avoid interference.
Decoupling capacitors should always be placed very close to the power pins of ICs. These capacitors help stabilize voltage and filter high-frequency noise.
Good routing practice improves reliability and prevents unexpected behavior in the final hardware.
Importance of the Ground Plane
One extremely important part of PCB design is the ground plane.
A ground plane is a large copper area connected to the circuit ground (GND). Instead of routing ground using thin traces, the PCB design uses a large copper fill that spreads across the board.
Using a ground plane provides several advantages:
First, it significantly reduces electrical noise. High-speed signals such as SPI and I²C generate switching currents, and a solid ground plane provides a stable reference that prevents signal distortion.
Second, a ground plane helps improve signal integrity by reducing impedance and minimizing electromagnetic interference.
Third, it helps with thermal dissipation, allowing heat to spread more evenly across the board.
For this reason, many PCB designs use one full layer dedicated primarily to ground, especially in two-layer boards where the bottom layer often becomes the ground plane.
In this smartwatch PCB, a ground plane was added to ensure stable communication between the ESP32-C3 and the sensors.
Final PCB Checks Before Manufacturing
Before sending the PCB for fabrication, the design should be verified using built-in tools such as Design Rule Check (DRC).
DRC ensures that:
- Trace widths meet manufacturing limits
- Spacing between copper features is sufficient
- Pads and vias follow the fabrication constraints
This step helps prevent manufacturing errors and ensures the board can be fabricated correctly.
Exporting Manufacturing Files
Once the PCB design is finalized, the layout software generates Gerber files.
Gerber files are the standard format used by PCB manufacturers. These files contain all the necessary information for fabrication, including:
- Copper layers
- Solder mask layers
- Silkscreen markings
- Drill holes
- Board outline
The Gerber package is typically exported as a ZIP file, which will later be uploaded to a PCB manufacturing service.
Ordering the PCB
After generating the Gerber files, the PCB can be ordered from online manufacturing services.
Popular PCB manufacturers include:
- JLCPCB
- PCBWay
- Seeed Fusion
- OSH Park
- Aisler
The ordering process is usually straightforward.
First, visit the website of the PCB manufacturer.
Upload the Gerber ZIP file generated by the PCB design software.
The website will automatically analyze the files and display a preview of the PCB.
Next, choose the board specifications. Common settings include:
- Board thickness (typically 1.6 mm)
- Number of layers (usually 2 layers for small projects)
- Solder mask color
- Surface finish (HASL or ENIG)
After confirming the specifications, select the quantity of boards and place the order.
Most PCB services manufacture boards within a few days, and the finished PCBs are shipped directly to your address.
PCB Assembly
2.1 — Prepare Your Workspace
Before turning on the soldering iron, set up your workspace so the assembly process is efficient and safe.
Place the PCB in a PCB holder or helping hands tool. Avoid holding the board directly with your fingers, since movement while soldering can easily cause poor joints.
Arrange all SMD components in a tray or on an anti-static mat so they are easy to access and don’t get lost. Small components like 0603 resistors and capacitors disappear surprisingly fast if your workspace is messy.
Make sure the following tools are within reach:
- Solder wire (0.3–0.6 mm)
- Flux pen
- Anti-static tweezers
- Magnifier or loupe for inspecting solder joints
- Multimeter set to continuity mode for quick electrical checks after soldering
Keep drinks away from the workbench. Flux and molten solder can splash if contaminated with liquid, which can damage the board and create a safety hazard.
2.2 — Set the Soldering Iron Temperature
Using the correct soldering temperature is important. Too low and the solder joint will be weak (a cold joint). Too high and components or PCB pads can be damaged.
Choose the temperature based on the type of solder you are using.
For Sn63/Pb37 solder (leaded solder), set the iron to 300–320 °C.
This solder melts at 183 °C, so this temperature range provides good flow without overheating components.
For lead-free solder (SnAgCu / RoHS), set the iron to 340–360 °C.
Lead-free solder melts at about 217 °C, which requires a slightly higher working temperature.
If you are using a Pinecil or TS101 soldering iron, enabling the boost mode can help the iron reach working temperature quickly when starting a solder joint.
When working with small SMD components, try to keep the tip temperature below about 300 °C whenever possible to reduce the risk of overheating the parts.
2.3 — Recommended Component Assembly Order
When assembling a PCB, it is best to solder smaller and lower-profile components first, then move to larger components.
This prevents previously installed parts from blocking access to smaller pads.
Recommended order for this project:
- 0603 resistors and capacitors
- These are the smallest parts on the board and should be installed first.
- Schottky diodes (1N5819)
- Make sure the cathode orientation matches the PCB marking.
- 3.3 µH inductor (L1)
- MOSFETs (Q1 and Q2)
- Carefully check the orientation against the datasheet or PCB silkscreen.
- ICsLTH7 Li-Po charging IC
- ST1340BB buck converter
- ESP32-C3 Super Mini module and header connections
- Sensor modules lastBMI160
- MAX30100
These are usually connected via headers or soldered directly once the rest of the board is assembled.
2.4 — Soldering Technique for 0603 Components
For small SMD parts such as 0603 resistors and capacitors, a reliable method is the “tin one pad first” technique.
First, apply a small amount of flux to the pads. Flux improves solder flow and helps prevent cold joints.
Next, pre-tin one pad by applying a small amount of solder.
Using tweezers, place the component onto the pads. Heat the pre-tinned pad while holding the component in place so the solder melts and anchors the part.
After the component is fixed on one side, solder the second pad normally by touching the iron to the pad and feeding a small amount of solder.
A typical solder joint should take about 1–2 seconds. If heating takes much longer, the PCB pad may start to lift or delaminate.
A good solder joint should appear smooth, slightly rounded, and shiny.
A dull or flat joint usually indicates a cold joint.
If solder accidentally bridges two pads, apply a little more flux and gently drag a clean soldering iron tip across the pads. In many cases the excess solder will wick back to the tip and remove the bridge.
2.5 — Inspection and Electrical Verification
Do not immediately connect power after finishing the soldering process. Performing a few quick checks can prevent damage to the board.
Start with a visual inspection using a loupe or magnifier. Look for solder bridges, especially around IC pins and MOSFET pads.
Next, perform a few measurements with a multimeter:
Check continuity between GND and the 3.3 V rail.
The reading should be open (no short).
Check VIN to GND.
This should also be open before the protection diode.
Verify the I²C pull-up resistors.
Measure from SDA and SCL to 3.3 V. The resistance should be approximately 4.7 kΩ.
If there is leftover flux residue, clean the board using 70 % or higher isopropyl alcohol and a soft brush. Flux residue can become corrosive over time if left on the PCB.
Once everything looks correct, connect the board to USB using a current-limited source, such as a power bank or USB tester.
A typical idle current for the board (with sensors inactive) should be around 50–80 mA.
If the current immediately rises above 200 mA, disconnect power immediately. This usually indicates a short circuit or incorrectly soldered component.
Using the BMI160 Built-In Step Counter
The smartwatch uses the BMI160 inertial measurement unit (IMU) to detect motion and count steps. While the BMI160 includes both an accelerometer and a gyroscope, the chip also contains an internal DSP engine capable of running motion algorithms directly inside the sensor.
Instead of implementing a full pedometer algorithm on the ESP32-C3, this project uses the hardware step counter feature built into the BMI160.
This approach has two major advantages.
First, it reduces CPU load. The ESP32-C3 only needs to read the final step count value from the sensor instead of continuously processing raw accelerometer data.
Second, it reduces power consumption. The sensor’s internal DSP performs the step detection while the microcontroller can remain idle or in low-power mode.
This is especially important because the ESP32-C3 is a single-core microcontroller, so offloading motion processing to the sensor keeps the system responsive and energy efficient.
BMI160 Internal Step Counter Registers
The pedometer engine inside the BMI160 is controlled using several internal registers.
The register named REG_CHIP_ID is located at address 0x00. This register identifies the silicon inside the chip and should return the value 0xD1. Reading this value confirms that communication with the sensor is working correctly.
The register REG_ACC_RANGE at address 0x41 controls the accelerometer sensitivity range. In this project the value is set to 0x03, which corresponds to ±2G mode. This range is ideal for human motion detection because walking acceleration rarely exceeds this range.
The step counter value is stored in two registers.
The register REG_STEP_CNT_0 at address 0x78 contains the least significant byte of the step count.
The register REG_STEP_CNT_1 at address 0x79 contains the most significant byte.
Together these two registers form a 16-bit step counter value, which means the sensor can count up to 65,535 steps before wrapping.
The register REG_STEP_CONF0 located at address 0x7A enables and configures the pedometer engine. In this implementation the register is set to the value 0x15, which activates the internal step detection logic.
The register REG_STEP_CONF1 located at address 0x7B controls the sensitivity and operating mode of the pedometer. The value used here is 0x0B, which corresponds to normal detection mode.
Finally, the REG_CMD register at address 0x7E is used for sending commands to the sensor. Writing the value 0xB2 resets the internal step counter so counting starts from zero.
I2C Communication Setup
The BMI160 communicates with the ESP32 using the I²C interface.
In this hardware design, the sensor address is fixed to 0x69 because the SDO pin is connected to HIGH. This address must be used when communicating with the device.
If the address is incorrect, the sensor will not acknowledge any communication attempts.
Sensor Startup Sequence
Startup sequence (order matters ⚠️)
* The 81ms gyro delay is critical. Skipping it causes the pedometer engine to start unstable and miss early steps.
Reading step count — burst read
Both bytes are read in a single I²C transaction starting at 0x78. This cuts bus overhead to under 0.5ms per read.
Before the step counter can operate correctly, the BMI160 requires a specific initialization sequence.
First, a soft reset command (0xB6) is sent to the command register. After issuing this reset, the system waits about 10 milliseconds to allow the internal registers to reinitialize.
Next, the accelerometer is placed into normal operating mode by sending the command 0x11. After this command, a delay of about 4 milliseconds is required.
Then the gyroscope is enabled using the command 0x15. Even though the pedometer mainly relies on accelerometer data, enabling the gyroscope helps stabilize the motion detection system. This step requires a longer delay of approximately 81 milliseconds to ensure the sensor is fully ready.
Finally, the step counter is reset by sending the command 0xB2, which clears any previous step count stored inside the chip.
After this sequence completes, the internal pedometer engine begins tracking steps automatically.
Efficient Step Reading
To retrieve the current step count, the firmware reads the two step counter registers starting at address 0x78.
Instead of performing two separate reads, the system uses an I²C burst read, which retrieves both bytes in a single transaction. This reduces bus overhead and improves efficiency.
The ESP32-C3 only needs less than half a millisecond to retrieve the updated step count from the sensor.
Because the algorithm runs inside the BMI160 hardware, the microcontroller does not need to constantly process accelerometer data.
Built-In Step Filtering
The BMI160 pedometer engine includes an internal step filtering buffer.
The chip will not immediately increase the step count when motion is detected. Instead, it waits until approximately seven consecutive steps occur before confirming a walking pattern.
This filtering mechanism prevents false step detection caused by random movements or vibrations.
As a result, the system becomes both more accurate and more energy efficient, because the microcontroller does not need to wake up repeatedly to validate step data.
Sensitivity Tuning
The pedometer sensitivity can be adjusted by modifying the configuration register.
Currently the device operates in normal mode, which provides a balanced detection threshold suitable for most walking speeds.
If higher sensitivity is required, the register controlling the pedometer configuration can be changed to enable sensitive mode. This mode detects slower or softer walking movements more easily.
However, increasing sensitivity may also increase the chance of detecting false steps.
Calibration for Better Accuracy
For improved accuracy, it is recommended to perform accelerometer offset calibration when the device is first manufactured.
This calibration measures the gravitational reference on the Z-axis and compensates for small sensor offsets.
Proper calibration improves the reliability of step detection and ensures the pedometer algorithm receives clean acceleration data.
Firmware Development (Heart Rate Sensor)
After the hardware is assembled, the next step is developing the firmware that runs on the ESP32-C3. The firmware is responsible for managing the display, handling user input, reading sensors, and controlling the system’s power behavior.
The smartwatch firmware is written in C++ using the Arduino framework, which provides easy access to ESP32 hardware features such as I²C communication, GPIO control, and power management.
Two sensors are connected through the shared I²C bus:
- MAX30100 for heart rate monitoring
- BMI160 for motion tracking and step counting
In this step we focus on the MAX30100 heart rate sensor, which provides real-time pulse detection using optical measurement.
How the MAX30100 Heart Rate Sensor Works
The MAX30100 is an integrated pulse oximeter and heart rate sensor.
Inside the sensor there are two LEDs:
- A red LED
- An infrared LED
These LEDs shine light into the user’s skin. When blood flows through the capillaries, it slightly changes how much light is absorbed and reflected. The sensor measures these changes using a photodiode.
Every time the heart beats, the blood volume changes slightly, causing a measurable variation in the reflected light. This signal is processed to determine the heart rate in beats per minute (BPM).
This technique is called photoplethysmography (PPG) and is widely used in smartwatches and fitness trackers.
I²C Communication Setup
The MAX30100 communicates with the ESP32-C3 using the I²C bus.
Both sensors in the system share the same bus lines:
SDA is connected to GPIO 8
SCL is connected to GPIO 9
Because I²C supports multiple devices, both the MAX30100 and BMI160 can operate on the same two wires without interfering with each other.
The ESP32 acts as the I²C master, periodically reading measurement data from the MAX30100.
Polling the Sensor Data
The MAX30100 stores measurement samples inside an internal FIFO buffer.
If the firmware does not read the FIFO frequently enough, the buffer may overflow and measurement data can be lost. To prevent this, the driver periodically polls the sensor and retrieves all available samples.
Instead of reading one sample at a time, the firmware uses burst polling, which reads multiple samples in a single transaction. This reduces I²C overhead and improves efficiency.
The driver performs several polling passes during each measurement cycle to ensure the FIFO buffer remains empty and no data is lost.
Heart Rate Data Processing
The raw signal coming from the MAX30100 contains noise and variations caused by movement, sensor position, and ambient light.
To obtain stable heart rate readings, the firmware performs several processing steps.
First, the raw infrared signal is analyzed to detect pulse peaks, which correspond to heartbeats.
Next, the time interval between consecutive peaks is calculated. From this interval, the firmware computes the beats per minute (BPM).
To improve stability, recent heart rate values are stored in a short history buffer. This allows the firmware to smooth the output and also enables the UI to draw a small real-time graph of the heart rate signal.
Sensor Power Management
Because the smartwatch runs on a battery, sensors must be carefully managed to avoid unnecessary power consumption.
When the heart rate mode is not active, the MAX30100 driver disables the sensor and stops polling the FIFO buffer. This prevents the sensor from continuously drawing current.
When the user enters the heart rate measurement screen, the sensor is initialized again and measurement begins.
Before the device enters deep sleep, the firmware shuts down the MAX30100 to ensure that no current is leaking through the I²C bus or sensor circuitry.
This shutdown process is part of the system’s power management strategy described earlier.
Displaying Heart Rate Data
The measured heart rate values are displayed on the TFT screen together with a small graph showing the recent signal waveform.
To keep the interface responsive and reduce CPU usage, the display system only updates the parts of the screen that change.
Instead of redrawing the entire screen each frame, the firmware performs partial rendering, updating only the numerical value and the graph region.
This approach significantly reduces rendering workload and helps keep the system smooth even with limited hardware resources.
Stability and Debug Protection
During development, debug messages are sometimes printed to the serial console.
However, sending serial messages continuously can waste CPU time and may even block execution if the USB buffer becomes full.
To prevent this, debug output is only executed when a serial connection is actually active.
This simple guard ensures that the smartwatch behaves normally when running from battery without a connected computer.
CPU Frequency Optimization (Underclock Strategy)
Because the smartwatch runs from a small LiPo battery, power efficiency is extremely important. One effective technique used in this project is dynamic CPU frequency scaling, which adjusts the ESP32-C3 clock speed depending on what the system is doing.
The ESP32-C3 normally runs at 160 MHz, but running at full speed all the time wastes energy when the system is performing simple tasks.
To solve this, the firmware dynamically changes the CPU frequency during operation.
When the system needs high performance, such as during menu animations or UI transitions, the CPU temporarily runs at 160 MHz. This ensures that animations remain smooth and responsive.
During normal operation, such as displaying the watchface, reading sensors, or running timers, the system switches to a lower clock speed of 80 MHz. This provides a good balance between performance and power consumption.
In the future, even lower frequencies such as 10–40 MHz can be used for features like Always-On Display (AOD) mode, where only minimal processing is required.
Reducing the CPU frequency significantly decreases power consumption because the processor performs fewer switching operations per second. This helps extend battery life without affecting user experience.
Another advantage of this approach is that heavy tasks are completed quickly at high frequency and then the processor returns to a lower power state. This method is often called “race to sleep”, where the processor finishes work quickly and then returns to an energy-efficient state.
Step — Source Code and Firmware Repository
The full firmware source code for this smartwatch is available on GitHub.
========================================================================================
My Github Repo:
hazzma/ESP-WATCH-V3-ST7789-C3-Supermini
========================================================================================
The repository contains the complete project including:
- Sensor drivers (MAX30100 and BMI160)
- Power management system
- Display control and UI rendering
- Step counter implementation
- Heart rate monitoring logic
If you want to build the firmware yourself or explore the code, download the project from the repository and open it in your development environment.
When cloning the repository, it is recommended to follow the newest branch, since it contains the latest optimizations and improvements to the firmware architecture.
The newest branch includes improvements such as:
- Optimized memory usage for UI rendering
- Improved sensor polling efficiency
- Enhanced power management logic
- Reduced CPU overhead for background tasks
Make sure to check out the latest branch version to ensure you are working with the most optimized codebase.
Example workflow:
- Clone the repository from GitHub
- Switch to the newest branch
- Build and flash the firmware to the ESP32-C3
This allows you to run the smartwatch firmware exactly as demonstrated in this project.
Troubleshooting and Known Issues
Step — Troubleshooting and Known Issues
Like most embedded systems, the smartwatch firmware relies on several low-level hardware features. If the system behaves unexpectedly during startup or programming, the issue is often related to CPU clock configuration and USB communication.
One important behavior to understand is how the ESP32-C3 interacts with USB CDC mode, which is used for flashing firmware and serial communication with a computer.
USB CDC Boot Issue
During early testing, it was discovered that running the ESP32-C3 at a reduced CPU frequency immediately after boot can sometimes cause the device to become stuck in USB CDC mode.
When the CPU is underclocked too early, the USB subsystem may fail to initialize properly. This can cause several symptoms:
- The device appears frozen after boot
- The USB serial port becomes unstable or stops responding
- The firmware upload process fails intermittently
- The system becomes difficult to reset or reconnect
This behavior can make the device appear as if it is “stressed” or locked in CDC mode.
Required Startup Configuration
To prevent this issue, the firmware forces the CPU clock to run at 160 MHz during initial startup.
Running the processor at full speed during the first seconds after boot ensures that:
- USB CDC initialization completes successfully
- The computer correctly detects the device
- Firmware flashing and serial communication remain stable
After the system has fully started and USB communication is stable, the firmware can safely switch to the lower power clock modes used during normal operation.
This approach ensures reliable development and debugging while still benefiting from power-saving techniques during regular use.
Recommended Debug Procedure
If the smartwatch does not behave correctly after flashing firmware, try the following steps:
First, confirm that the CPU is running at 160 MHz during startup before any dynamic frequency scaling occurs.
Second, check that the USB cable and port are functioning properly and that the device appears correctly in the system’s serial port list.
Finally, reflash the firmware and reset the board if the device becomes stuck during boot.
Following these steps usually resolves startup issues related to USB CDC communication.
Tiny engineering lesson hiding here. Low-power optimization is great, but timing matters. If you start underclocking things before critical subsystems finish initializing, weird behavior shows up.
Your fix is actually the standard trick used in many embedded devices:
boot fast → stabilize system → then save power.
Simple idea, surprisingly effective.