In previous lessons, we learned about two WiFi modes:
In real projects, we often need to combine the advantages of both modes:
| Requirement | STA Mode | AP Mode |
|---|---|---|
| Access internet data | ✅ Connects to router, accesses internet | ❌ Cannot access internet |
| Local control | ❌ Phone must share router | ✅ Phone connects directly to Arduino |
| Works without router | ❌ Requires router | ✅ Standalone operation |
| NTP time sync | ✅ Can sync | ❌ Cannot sync |
Core concept: On startup, use STA mode to connect to the router and fetch network time, then switch to AP mode to create a hotspot for phone control. This way, we can both access internet data and work independently!
Simple analogy:
NTP (Network Time Protocol) is a protocol for synchronizing the time of computers across a network. By connecting to an NTP server on the internet, Arduino can obtain the current accurate time.
This project uses the Chinese National Time Service Center's NTP server cn.ntp.org.cn with the timezone set to UTC+8 (Beijing time).
If you are located in other regions or need high-availability servers, you can replace the server address and adjust the timezone offset accordingly:
| Region / Country | Recommended NTP Server | Organization / Feature | Timezone Offset |
|---|---|---|---|
| China | ntp.aliyun.comcn.ntp.org.cn |
Alibaba Cloud / National Time Service Center | 8 * 3600 (UTC+8) |
| Global (Anycast) | time.cloudflare.comtime.google.com |
Cloudflare / Google (Fastest routing worldwide) | Regional dependent |
| United States | time.nist.govus.pool.ntp.org |
NIST Official / US NTP Pool | -5 * 3600 (EST)-8 * 3600 (PST) |
| Japan | ntp.nict.jpjp.pool.ntp.org |
NICT Official Atomic Clock | 9 * 3600 (UTC+9) |
| Europe / Germany | ptbtime1.ptb.deeurope.pool.ntp.org |
PTB (National Metrology Institute of Germany) | 1 * 3600 (UTC+1) |
| United Kingdom | uk.pool.ntp.org |
UK NTP Pool | 0 (UTC+0) |
| Singapore | sg.pool.ntp.org |
Singapore NTP Pool | 8 * 3600 (UTC+8) |
💡 Tip: Global Anycast servers like
time.cloudflare.comortime.google.comautomatically connect your Arduino to the nearest data center worldwide, making them ideal choices regardless of location.
The UNO R4 WiFi board cannot use STA and AP modes simultaneously — it can only operate in one mode at a time. So we need to:
After switching to AP mode, Arduino can no longer access the internet for time updates, so we need a local timekeeping solution:
millis() function returns the number of milliseconds elapsed since Arduino booted up.| Function | Description | Purpose in This Project |
|---|---|---|
WiFi.disconnect() |
Disconnects current WiFi connection | Clears previous state |
WiFi.begin(ssid, pass) |
Starts STA mode, connects to router | Connects Arduino as a client to WiFi |
WiFi.status() |
Checks WiFi connection status | Confirms successful router connection |
WiFi.end() |
Completely shuts down WiFi | Releases resources, prepares for mode switch |
| Function | Description | Purpose in This Project |
|---|---|---|
WiFi.beginAP(ssid, pass) |
Starts AP mode, creates WiFi hotspot | Turns Arduino into a WiFi hotspot |
WiFi.localIP() |
Gets the device's IP address | Checks the default IP in AP mode (usually 192.168.4.1) |
| Function | Description | Purpose in This Project |
|---|---|---|
WiFiUDP ntpUDP |
Creates UDP object | NTP communication requires UDP protocol |
NTPClient timeClient(...) |
Creates NTP client | Configures NTP server and timezone |
timeClient.begin() |
Starts NTP client | Begins NTP communication |
timeClient.forceUpdate() |
Forces immediate NTP request | Bypasses 60s cooldown to reliably fetch time |
timeClient.getEpochTime() |
Gets Unix timestamp | Gets current total seconds count |
timeClient.getFormattedTime() |
Gets formatted time string | Returns current time formatted as HH:MM:SS |
timeClient.end() |
Stops NTP client | Releases NTP socket resources |
| Function | Description | Purpose in This Project |
|---|---|---|
millis() |
Returns milliseconds since boot | Checks if 1 second has elapsed |
currentUnixTime++ |
Increments timestamp by 1 | Implements per-second timekeeping |
| Function | Description | Purpose in This Project |
|---|---|---|
WiFiServer server(80) |
Creates web server on port 80 | Listens for HTTP requests |
server.begin() |
Starts web server | Begins accepting client connections |
server.accept() |
Accepts new client connection | Gets client object |
client.readStringUntil('\r') |
Reads client's HTTP request | Parses control commands |
client.println() |
Sends data to client | Returns HTML webpage |
client.stop() |
Closes client connection | Completes communication |
HTTP requests consist of multiple lines of text; the first line tells the server what the client wants. This project uses two types of requests:
| Request Path | Meaning | Corresponding Action |
|---|---|---|
GET /H |
"High" - high voltage | Turn LED on |
GET /L |
"Low" - low voltage | Turn LED off |
// STA mode settings (connecting to router)
const char* sta_ssid = "ZNP"; // Change to your WiFi name
const char* sta_password = "12345678"; // Change to your WiFi password
// AP mode settings (creating hotspot)
const char* ap_ssid = "R4_AP"; // Hotspot name, customizable
const char* ap_password = "12345678"; // Hotspot password, min 8 chars
Notes:
sta_ssid and sta_password must be changed to your home router's WiFi name and password.cn.ntp.org.cn.#include <WiFiS3.h> // Main WiFi library for UNO R4 WiFi
#include <WiFiUdp.h> // UDP protocol library (required by NTP)
#include <NTPClient.h> // NTP client library
// STA and AP WiFi configuration
const char* sta_ssid = "ZNP"; // Router name
const char* sta_password = "12345678"; // Router password
const char* ap_ssid = "R4_AP"; // Hotspot name
const char* ap_password = "12345678"; // Hotspot password
WiFiServer server(80); // Create web server on port 80
WiFiUDP ntpUDP; // Create UDP object
NTPClient timeClient(ntpUDP, "cn.ntp.org.cn", 8 * 3600); // Configure NTP client (UTC+8)
int led = LED_BUILTIN; // On-board LED pin
// Time variables (defaults to 12:00:00 as initial fallback)
unsigned long currentUnixTime = 12 * 3600L; // Current timestamp in seconds
unsigned long lastUpdateTime = 0; // Last update time (milliseconds)
Line-by-line explanation:
#include <WiFiS3.h>: Includes the main WiFi library containing AP/STA modes, web server, and other WiFi features.#include <WiFiUdp.h>: Includes the UDP library; NTP protocol uses UDP for communication.#include <NTPClient.h>: Includes the NTP client library for fetching network time.NTPClient timeClient(ntpUDP, "cn.ntp.org.cn", 8 * 3600): Creates an NTP client, specifying the server address and timezone offset (UTC+8 = 8 hours = 28800 seconds).currentUnixTime = 12 * 3600L: Initializes the timestamp to 12:00:00 (43200 seconds) as a fallback in case time synchronization fails.lastUpdateTime: Records the millis() value at the last time update.setup() - Initializationvoid setup() {
Serial.begin(115200);
while (!Serial);
delay(1000);
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
Serial.println("=== Boot: Get Time → Switch to AP ===");
getNetTime();
startAPMode();
server.begin();
lastUpdateTime = millis();
}
Step breakdown:
getNetTime() to connect to router via STA mode and sync NTP time.startAPMode() to create a WiFi hotspot.server.begin() to start the HTTP service, and record millis() as the starting reference for software timekeeping.getNetTime() - Get Network Time via STA Modevoid getNetTime() {
WiFi.disconnect();
delay(500);
WiFi.begin(sta_ssid, sta_password);
Serial.print("Connecting to router: ");
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout < 40) {
delay(500);
Serial.print(".");
timeout++;
}
bool syncSuccess = false;
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n✅ WiFi connected, syncing time...");
delay(1000); // Allow network and DNS stack to fully initialize
timeClient.begin();
for (int i = 0; i < 5; i++) {
if (timeClient.forceUpdate()) {
currentUnixTime = timeClient.getEpochTime();
Serial.print("✅ Time synced: ");
Serial.println(timeClient.getFormattedTime());
syncSuccess = true;
break;
}
delay(1500);
}
timeClient.end();
} else {
Serial.println("\n❌ WiFi connection timed out");
}
// Fallback to 12:00:00 if either WiFi or NTP sync failed
if (!syncSuccess) {
Serial.println("⚠️ Failed to sync network time, defaulting to 12:00:00");
currentUnixTime = 12 * 3600L;
}
WiFi.end();
delay(1000);
}
Step breakdown:
Step 1: Connect to router
WiFi.disconnect(); // Clear previous connections
WiFi.begin(sta_ssid, sta_password); // Connect to router
Step 2: Wait for connection
while (WiFi.status() != WL_CONNECTED && timeout < 40) {
delay(500); // Check every 0.5 seconds
timeout++; // Maximum wait: 20 seconds (40 × 0.5s)
}
Timeout mechanism: waits at most 20 seconds to avoid indefinite hanging.
Step 3: Force get network time
delay(1000); // Buffer delay for socket initialization
timeClient.begin();
timeClient.forceUpdate(); // Bypass internal 60-second cooldown and force live NTP transmission
Retries up to 5 times with 1.5-second intervals using forceUpdate() to guarantee each loop sends an actual UDP request packet.
Step 4: Handle failure fallback
if (!syncSuccess) {
Serial.println("⚠️ Failed to sync network time, defaulting to 12:00:00");
currentUnixTime = 12 * 3600L;
}
If WiFi connection fails or all 5 NTP attempts fail, currentUnixTime explicitly falls back to 12:00:00 (43200 seconds) so timekeeping starts from 12:00:00 rather than 00:00:00.
Step 5: Disconnect STA connection
timeClient.end(); // Stop NTP client
WiFi.end(); // Completely shut down WiFi, release resources
WiFi.end() completely closes the WiFi module, preparing for AP mode startup.
startAPMode() - Start AP Modevoid startAPMode() {
Serial.println("\n==================================");
Serial.println("📶 AP mode started");
Serial.println("Connect to WiFi: R4_AP");
Serial.println("Visit in browser: 192.168.4.1");
Serial.println("==================================\n");
WiFi.beginAP(ap_ssid, ap_password); // Create WiFi hotspot
delay(3000); // Wait 3 seconds for initialization
Serial.print("AP IP: ");
Serial.println(WiFi.localIP()); // Print IP address in AP mode
}
After startup, nearby phones/computers can detect the "R4_AP" WiFi hotspot. The default IP is 192.168.4.1.
loop() - Main Loopvoid loop() {
// === Core 1: Automatic timekeeping ===
if (millis() - lastUpdateTime >= 1000) {
lastUpdateTime = millis();
currentUnixTime++;
int h = (currentUnixTime % 86400L) / 3600;
int m = (currentUnixTime % 3600) / 60;
int s = currentUnixTime % 60;
Serial.print("⏰ Time: ");
if (h < 10) Serial.print("0"); Serial.print(h);
Serial.print(":");
if (m < 10) Serial.print("0"); Serial.print(m);
Serial.print(":");
if (s < 10) Serial.print("0"); Serial.println(s);
}
// === Core 2: AP mode web page LED control ===
WiFiClient client = server.accept();
if (!client) return;
String req = client.readStringUntil('\r');
client.flush();
if (req.indexOf("GET /H") != -1) {
digitalWrite(led, HIGH);
}
if (req.indexOf("GET /L") != -1) {
digitalWrite(led, LOW);
}
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
client.println("<!DOCTYPE html><html>");
client.println("<head><title>UNO R4 AP LED Control</title></head>");
client.println("<body style='text-align:center; margin-top:50px;'>");
client.println("<h1 style='color:blue;'>UNO R4 AP LED Control</h1>");
client.println("<a href='/H' style='font-size:22px; margin:20px;'>LED ON</a>");
client.println("<a href='/L' style='font-size:22px; margin:20px;'>LED OFF</a>");
client.println("</body></html>");
client.stop();
}
Step breakdown:
Core 1: Automatic timekeeping
if (millis() - lastUpdateTime >= 1000) { // Check if 1 second has passed
lastUpdateTime = millis(); // Update reference time
currentUnixTime++; // Increment timestamp by 1 second
// Calculate hours, minutes, seconds from timestamp
int h = (currentUnixTime % 86400L) / 3600; // Hours (0-23)
int m = (currentUnixTime % 3600) / 60; // Minutes (0-59)
int s = currentUnixTime % 60; // Seconds (0-59)
}
millis() returns the number of milliseconds since Arduino booted. By checking whether the difference is ≥ 1000 milliseconds, it updates the time once per second without blocking the execution of Web server interactions.
Timestamp to hours/minutes/seconds conversion formula:
h = (timestamp % 86400) / 3600: Get total seconds elapsed in the day, divide by 3600 to get hoursm = (timestamp % 3600) / 60: Get remaining seconds of the current hour, divide by 60 to get minutess = timestamp % 60: Get seconds of the current minuteCore 2: Web page LED control
server.accept() waits for client connection.readStringUntil('\r') reads the HTTP request line./H and /L commands to toggle LED state.client.stop() terminates the client socket.Arduino boots up
↓
┌───────────────────────────────────────────────┐
│ STA mode connects to router │
│ ├─ Success → Force sync via cn.ntp.org.cn │
│ └─ Failure/Timeout → Fallback to 12:00:00 │
└───────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────┐
│ Disconnect STA, start AP mode │
│ (creates WiFi hotspot "R4_AP") │
└───────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────┐
│ Start web server on port 80 │
└───────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────┐
│ Main loop() │
│ ├─ Software timekeeping (+1 per second) │
│ └─ Handle HTTP requests for LED control │
└───────────────────────────────────────────────┘
↓
Phone connects to "R4_AP" → Visit 192.168.4.1 → Control LED
Note: The serial monitor baud rate needs to be set to 115200 (not the default 9600).
On startup (time sync successful):
=== Boot: Get Time → Switch to AP ===
Connecting to router: ....
✅ WiFi connected, syncing time...
✅ Time synced: 14:30:25
==================================
📶 AP mode started
Connect to WiFi: R4_AP
Visit in browser: 192.168.4.1
==================================
AP IP: 192.168.4.1
⏰ Time: 14:30:26
⏰ Time: 14:30:27
⏰ Time: 14:30:28
...
On startup (WiFi connection succeeded, but NTP timed out):
=== Boot: Get Time → Switch to AP ===
Connecting to router: ....
✅ WiFi connected, syncing time...
⚠️ Failed to sync network time, defaulting to 12:00:00
==================================
📶 AP mode started
Connect to WiFi: R4_AP
Visit in browser: 192.168.4.1
==================================
AP IP: 192.168.4.1
⏰ Time: 12:00:01
⏰ Time: 12:00:02
⏰ Time: 12:00:03
...
On startup (WiFi connection failed entirely):
=== Boot: Get Time → Switch to AP ===
Connecting to router: ........................................
❌ WiFi connection timed out
⚠️ Failed to sync network time, defaulting to 12:00:00
==================================
📶 AP mode started
Connect to WiFi: R4_AP
Visit in browser: 192.168.4.1
==================================
AP IP: 192.168.4.1
⏰ Time: 12:00:01
⏰ Time: 12:00:02
...
NTPClient, install the version by Fabrice Weinberg.STA_and_AP.ino.sta_ssid to your home router's WiFi name.sta_password to your home router's WiFi password.ap_ssid and ap_password as needed.192.168.4.1.Q1: The serial monitor shows garbled characters?
A: Make sure the serial monitor baud rate is set to 115200 (the code uses Serial.begin(115200)).
Q2: Can't connect to router, "WiFi connection timed out" message?
A: Please check:
sta_ssid and sta_password are correct.Q3: Phone can't find the "R4_AP" hotspot?
A: Please check:
Q4: Why does the time tick from 12:00:01 instead of the real time?
A: This means NTP synchronization failed (due to network delay, packet loss, or DNS resolution issues). Rather than starting at 00:00:00, the code automatically caught the failure and defaulted to 12:00:00 as specified.
Q5: Why did time synchronization previously fail silently?
A: The NTPClient library has a default 60-second update interval when calling update(). If the initial attempt fails, subsequent calls in rapid loops are ignored. Using forceUpdate() bypasses this check, forcing actual network transmissions.
Q6: After the phone connects to the hotspot, is Arduino's time still accurate?
A: The phone connects to the AP hotspot and will not affect Arduino's software timekeeping. The time will continue ticking.
Q7: Can the phone access the internet after connecting?
A: No. In AP mode, Arduino only provides local network connectivity; the phone cannot access the internet through it.
Q8: Why can't STA and AP modes be used simultaneously?
A: The UNO R4 WiFi hardware driver library currently supports operating in only one mode at a time. This is a framework limitation, so we use the strategy of "first STA to get time, then AP to provide services."
Q9: Why is the output time a few seconds slower than the actual time over an extended period?
A: There are two primary factors:
millis() timing relies on the main board oscillator (48MHz main clock on the RA4M1 MCU). Natural frequency tolerance, ambient temperature fluctuations, and interrupt scheduling jitter can accumulate a slight deviation over long durations (typically a few seconds per day).| Problem | Possible Cause | Solution |
|---|---|---|
| Garbled serial | Wrong baud rate | Change to 115200 |
| Can't connect WiFi | Wrong password/5GHz | Check password, use 2.4GHz |
| Time starts at 12:00:00 | NTP timeout/DNS fail | Normal fallback; check router internet access |
| Can't find hotspot | Code not uploaded | Re-upload code |
| Webpage won't open | Wrong IP address | Use 192.168.4.1 |
| LED not responding | Wrong pin/browser cache | Refresh page, verify pin |
const char* sta_ssid = "YourWiFi"; // Change to your WiFi
const char* sta_password = "yourPassword"; // Change to your WiFi password
const char* ap_ssid = "MyArduino"; // Custom hotspot name
const char* ap_password = "secret1234"; // Custom password
// 1. High-reliability server for China (Alibaba Cloud, UTC+8)
NTPClient timeClient(ntpUDP, "ntp.aliyun.com", 8 * 3600);
// 2. Global Anycast server (Cloudflare, fast worldwide routing)
NTPClient timeClient(ntpUDP, "time.cloudflare.com", 8 * 3600);
// 3. Japan Official Time (NICT, UTC+9)
NTPClient timeClient(ntpUDP, "ntp.nict.jp", 9 * 3600);
// 4. US Eastern Time (NIST, UTC-5, note the negative sign)
NTPClient timeClient(ntpUDP, "time.nist.gov", -5 * 3600);
// Use a different fallback time if time synchronization fails
currentUnixTime = 8 * 3600L + 30 * 60L; // 08:30:00
In loop(), calculate hours, minutes, and seconds from currentUnixTime before generating the client response:
int h = (currentUnixTime % 86400L) / 3600;
int m = (currentUnixTime % 3600) / 60;
int s = currentUnixTime % 60;
client.print("<p>Current Time: ");
if (h < 10) client.print("0"); client.print(h);
client.print(":");
if (m < 10) client.print("0"); client.print(m);
client.print(":");
if (s < 10) client.print("0"); client.print(s);
client.println("</p>");
if (req.indexOf("GET /B") != -1) { // Blinking mode
for (int i = 0; i < 5; i++) {
digitalWrite(led, HIGH);
delay(200);
digitalWrite(led, LOW);
delay(200);
}
}
// Re-sync time every hour
// Note: Switching to STA mode will temporarily disconnect connected AP clients
unsigned long lastSyncTime = 0;
void loop() {
if (millis() - lastSyncTime > 3600000) { // 3600 seconds = 1 hour
lastSyncTime = millis();
resyncTime(); // Re-sync routine
}
// ... existing code
}
// Make LED blink rapidly when connection fails
if (WiFi.status() != WL_CONNECTED) {
for (int i = 0; i < 10; i++) {
digitalWrite(led, !digitalRead(led));
delay(100);
}
}
Create a dynamic clock webpage:
<!DOCTYPE html>
<html>
<head>
<title>UNO R4 Clock</title>
<script>
// Use JavaScript to implement dynamic clock
// Refresh page periodically or use fetch/AJAX to update time
</script>
</head>
<body>
<h1 id="clock">00:00:00</h1>
</body>
</html>
Trigger actions (ringing, turning on lights) at specified times:
if (h == 7 && m == 30 && s == 0) { // Every day at 7:30
digitalWrite(led, HIGH); // Turn on light
}
Connect a DHT11/DHT22 sensor and display temperature and humidity on the webpage:
client.print("<p>Temperature: ");
client.print(temperature);
client.println("°C</p>");
client.print("<p>Humidity: ");
client.print(humidity);
client.println("%</p>");
Allow multiple users to connect simultaneously:
while (true) {
WiFiClient client = server.available();
if (!client) break;
// Handle each client
}
Save time and sensor data to an SD card or send to a cloud platform via WiFi:
// Save to SD card
// Send to IoT platform
int led1 = 9;
int led2 = 10;
if (req.indexOf("GET /H1") != -1) digitalWrite(led1, HIGH);
if (req.indexOf("GET /L1") != -1) digitalWrite(led1, LOW);
if (req.indexOf("GET /H2") != -1) digitalWrite(led2, HIGH);
if (req.indexOf("GET /L2") != -1) digitalWrite(led2, LOW);
Periodically switch between STA and AP modes to maintain time accuracy. Note that whenever AP mode is stopped, any connected phone or PC will temporarily lose connection until AP is restarted.
void loop() {
if (needResync()) {
// Note: Disconnects current AP clients temporarily
getNetTime();
startAPMode();
server.begin();
}
// ...
}