Arduino 网络时钟客户端

Home / 博客 / Arduino 网络时钟客户端

更新! 11/15/2015
添加了WiFi和可充电电池选项(步骤10)。

更新!
增加了12h/24h 的开关,还有标准/ 夏令时开关!见步骤7 & 步骤8。

你是否曾想要一个和办公室时间来源完全准确的表?

这就有一个网络的办公时间服务器,你可以依据它并同步你的时间。大多数人用电脑来进行设置,现在Arduino也可以同样做到。(GPS时间客户端,详见 http://arduinotronics.blogspot.com/2014/03/gps-on-lcd.html

你只需要一个Arduino和一个以太网插板,但是我们也加一个LCD显示屏。随后可能还增加闹钟功能。

Arduino UNO
Arduino Ethernet Shield

可选:

串口 LCD 显示屏 

步骤1:连接硬件

首先,记下印在以太网插板顶端的MAC地址。你接下来会用到。

看起来像 90 A2 DA 00 23 36,但是在代码中插入需要变成 0×90,0xA2,0xDA,0×00,0×23,0×36.

将以太网插板查到Arduino UNO顶部。用一根网线把它连到路由器上。

步骤2:代码

只需要在Arduino库文件夹下额外安装一个库。那就是 Time Library,可找 http://www.pjrc.com/teensy/td_libs_Time.html

你讲需要以太网插板顶部的那个MAC地址,但是IP,网关,子网掩码都可以通过DHCP获得。你还需要时间服务器地址(见下面步骤)。

需要下载到Arduino上的程序如下:

//sample code originated at http://www.openreefs.com/ntpServer
//modified by Steve Spence, http://arduinotronics.blogspot.com

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <Time.h>

/* ******** Ethernet Card Settings ******** */
// Set this to your Ethernet Card Mac Address
byte mac[] = { 0×90, 0xA2, 0xDA, 0×00, 0×23, 0×36 };

/* ******** NTP Server Settings ******** */
/* us.pool.ntp.org NTP server
(Set to your time server of choice) */
IPAddress timeServer(216, 23, 247, 62);

/* Set this to the offset (in seconds) to your local time
This example is GMT – 4 */
const long timeZoneOffset = -14400L;

/* Syncs to NTP server every 15 seconds for testing,
set to 1 hour or more to be reasonable */
unsigned int ntpSyncTime = 3600;

/* ALTER THESE VARIABLES AT YOUR OWN RISK */
// local port to listen for UDP packets
unsigned int localPort = 8888;
// NTP time stamp is in the first 48 bytes of the message
const int NTP_PACKET_SIZE= 48;
// Buffer to hold incoming and outgoing packets
byte packetBuffer[NTP_PACKET_SIZE];
// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;
// Keeps track of how long ago we updated the NTP server
unsigned long ntpLastUpdate = 0;
// Check last time clock displayed (Not in Production)
time_t prevDisplay = 0;

void setup() {
Serial.begin(9600);

// Ethernet shield and NTP setup
int i = 0;
int DHCP = 0;
DHCP = Ethernet.begin(mac);
//Try to get dhcp settings 30 times before giving up
while( DHCP == 0 && i < 30){
delay(1000);
DHCP = Ethernet.begin(mac);
i++;
}
if(!DHCP){
Serial.println(“DHCP FAILED”);
for(;;); //Infinite loop because DHCP Failed
}
Serial.println(“DHCP Success”);

//Try to get the date and time
int trys=0;
while(!getTimeAndDate() && trys<10) {
trys++;
}
}

// Do not alter this function, it is used by the system
int getTimeAndDate() {
int flag=0;
Udp.begin(localPort);
sendNTPpacket(timeServer);
delay(1000);
if (Udp.parsePacket()){
Udp.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer
unsigned long highWord, lowWord, epoch;
highWord = word(packetBuffer[40], packetBuffer[41]);
lowWord = word(packetBuffer[42], packetBuffer[43]);
epoch = highWord << 16 | lowWord;
epoch = epoch – 2208988800 + timeZoneOffset;
flag=1;
setTime(epoch);
ntpLastUpdate = now();
}
return flag;
}

// Do not alter this function, it is used by the system
unsigned long sendNTPpacket(IPAddress& address)
{
memset(packetBuffer, 0, NTP_PACKET_SIZE);
packetBuffer[0] = 0b11100011;
packetBuffer[1] = 0;
packetBuffer[2] = 6;
packetBuffer[3] = 0xEC;
packetBuffer[12]  = 49;
packetBuffer[13]  = 0x4E;
packetBuffer[14]  = 49;
packetBuffer[15]  = 52;
Udp.beginPacket(address, 123);
Udp.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}

// Clock display of the time and date (Basic)
void clockDisplay(){
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(” “);
Serial.print(day());
Serial.print(” “);
Serial.print(month());
Serial.print(” “);
Serial.print(year());
Serial.println();
}

// Utility function for clock display: prints preceding colon and leading 0
void printDigits(int digits){
Serial.print(“:”);
if(digits < 10)
Serial.print(’0′);
Serial.print(digits);
}

// This is where all the magic happens…
void loop() {
// Update the time via NTP server as often as the time you set at the top
if(now()-ntpLastUpdate > ntpSyncTime) {
int trys=0;
while(!getTimeAndDate() && trys<10){
trys++;
}
if(trys<10){
Serial.println(“ntp server update success”);
}
else{
Serial.println(“ntp server update failed”);
}
}

// Display the time if it has changed by more than a second.
if( now() != prevDisplay){
prevDisplay = now();
clockDisplay();
}
}

步骤3:时钟服务器地址

如果你知道一个工作时间服务器IP地址,在你的代码中键入它。

/* ******** NTP Server Settings ******** */
/* us.pool.ntp.org NTP server
(Set to your time server of choice) */
IPAddress timeServer(216, 23, 247, 62);

Otherwise, run this sketch to get a valid time server ip. If you really want to get techy, merge the following code into the main sketch so that it finds a valid time server on every update. Don’t forget to update your MAC address below.

/*
DHCP-based IP printer

This sketch uses the DHCP extensions to the Ethernet library
to get an IP address via DHCP and print the address obtained.
using an Arduino Wiznet Ethernet shield.

Circuit:
* Ethernet shield attached to pins 10, 11, 12, 13

created 12 April 2011
by Tom Igoe

*/

#include <SPI.h>
#include <Ethernet.h>
#include <Dns.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = { 0×00, 0xAA, 0xBB, 0xCC, 0xDE, 0×02 };

// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
EthernetClient client;

void setup() {
// start the serial library:
Serial.begin(9600);

pinMode(4,OUTPUT);
digitalWrite(4,HIGH);

// start the Ethernet connection:
if (Ethernet.begin(mac) == 0) {
Serial.println(“Failed to configure Ethernet using DHCP”);
// no point in carrying on, so do nothing forevermore:
for(;;)
;
}
// print your local IP address:
Serial.print(“My IP address: “);
for (byte thisByte = 0; thisByte < 4; thisByte++) {
// print the value of each byte of the IP address:
Serial.print(Ethernet.localIP()[thisByte], DEC);
Serial.print(“.”);
}
Serial.println();
IPAddress testIP;

DNSClient dns;
dns.begin(Ethernet.dnsServerIP());
dns.getHostByName(“pool.ntp.org”,testIP);
Serial.print(“NTP IP from the pool: “);
Serial.println(testIP);

}

void loop() {
}

步骤4:时间偏移

你需要加入你的时区的时间偏移量。就像我当前在East Coast DayLight Saving Time,我需要-14400,这是和GMT(世界时)实际那相差的秒数。可参看下表:

http://www.epochconverter.com/epoch/timezones.php

在代码中找到这部分:

/* Set this to the offset (in seconds) to your local time
This example is GMT – 4 */
const long timeZoneOffset = -14400L;

步骤5:它能工作了吗?

至此,硬件已连接好(UNO和以太网插板),连接路由器,把你的MAC地址和时钟服务器地址输入到Arduino中,你应该能看到类似如下:

步骤6:加LCD屏

如果你正使用串口LCD屏,现在就能连上。这有一个额外的库,是I2C(接口)LCD库。你可以在这找到:

http://arduinotronics.blogspot.com/2014/02/sainsmart-i2c-lcd.html

LCD       Arduino UNO

SCL        A5
SDA       A4
VCC       +5v
GND       Gnd

上述带有LCD增加项的NTP代码如下:

//sample code originated at http://www.openreefs.com/ntpServer
//modified by Steve Spence, http://arduinotronics.blogspot.com

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <Time.h>
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

//LCD Settings

#define I2C_ADDR    0x3F // <<—– Add your address here.  Find it from I2C Scanner
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

/* ******** Ethernet Card Settings ******** */
// Set this to your Ethernet Card Mac Address
byte mac[] = { 0×90, 0xA2, 0xDA, 0×00, 0×23, 0×36 };

/* ******** NTP Server Settings ******** */
/* us.pool.ntp.org NTP server
(Set to your time server of choice) */
IPAddress timeServer(216, 23, 247, 62);

/* Set this to the offset (in seconds) to your local time
This example is GMT – 4 */
const long timeZoneOffset = -14400L;

/* Syncs to NTP server every 15 seconds for testing,
set to 1 hour or more to be reasonable */
unsigned int ntpSyncTime = 3600;

/* ALTER THESE VARIABLES AT YOUR OWN RISK */
// local port to listen for UDP packets
unsigned int localPort = 8888;
// NTP time stamp is in the first 48 bytes of the message
const int NTP_PACKET_SIZE= 48;
// Buffer to hold incoming and outgoing packets
byte packetBuffer[NTP_PACKET_SIZE];
// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;
// Keeps track of how long ago we updated the NTP server
unsigned long ntpLastUpdate = 0;
// Check last time clock displayed (Not in Production)
time_t prevDisplay = 0;

void setup() {
lcd.begin (16,2);

lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
lcd.setBacklight(HIGH);

Serial.begin(9600);

// Ethernet shield and NTP setup
int i = 0;
int DHCP = 0;
DHCP = Ethernet.begin(mac);
//Try to get dhcp settings 30 times before giving up
while( DHCP == 0 && i < 30){
delay(1000);
DHCP = Ethernet.begin(mac);
i++;
}
if(!DHCP){
Serial.println(“DHCP FAILED”);
for(;;); //Infinite loop because DHCP Failed
}
Serial.println(“DHCP Success”);

//Try to get the date and time
int trys=0;
while(!getTimeAndDate() && trys<10) {
trys++;
}
}

// Do not alter this function, it is used by the system
int getTimeAndDate() {
int flag=0;
Udp.begin(localPort);
sendNTPpacket(timeServer);
delay(1000);
if (Udp.parsePacket()){
Udp.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer
unsigned long highWord, lowWord, epoch;
highWord = word(packetBuffer[40], packetBuffer[41]);
lowWord = word(packetBuffer[42], packetBuffer[43]);
epoch = highWord << 16 | lowWord;
epoch = epoch – 2208988800 + timeZoneOffset;
flag=1;
setTime(epoch);
ntpLastUpdate = now();
}
return flag;
}

// Do not alter this function, it is used by the system
unsigned long sendNTPpacket(IPAddress& address)
{
memset(packetBuffer, 0, NTP_PACKET_SIZE);
packetBuffer[0] = 0b11100011;
packetBuffer[1] = 0;
packetBuffer[2] = 6;
packetBuffer[3] = 0xEC;
packetBuffer[12]  = 49;
packetBuffer[13]  = 0x4E;
packetBuffer[14]  = 49;
packetBuffer[15]  = 52;
Udp.beginPacket(address, 123);
Udp.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}

// Clock display of the time and date (Basic)
void clockDisplay(){
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(” “);
Serial.print(day());
Serial.print(” “);
Serial.print(month());
Serial.print(” “);
Serial.print(year());
Serial.println();

lcd.setCursor (0,0);
if (hour() < 10){
lcd.print(“0″); }
if (hour() > 12){
lcd.print(“0″);
lcd.print(hour()-12); } else {
lcd.print(hour()); }
lcd.print(“:”);
if (minute() < 10){
lcd.print(“0″); }
lcd.print(minute());
lcd.print(“:”);
if (second() < 10){
lcd.print(“0″); }
lcd.print(second());
if (hour() > 12){
lcd.print(” PM”); }
else {
lcd.print(” AM”); }

lcd.setCursor (0,1);
if (month() < 10){
lcd.print(“0″); }
lcd.print(month());
lcd.print(“/”);
if (day() < 10){
lcd.print(“0″); }
lcd.print(day());
lcd.print(“/”);
lcd.print(year());
}

// Utility function for clock display: prints preceding colon and leading 0
void printDigits(int digits){
Serial.print(“:”);
if(digits < 10)
Serial.print(’0′);
Serial.print(digits);
}

// This is where all the magic happens…
void loop() {
// Update the time via NTP server as often as the time you set at the top
if(now()-ntpLastUpdate > ntpSyncTime) {
int trys=0;
while(!getTimeAndDate() && trys<10){
trys++;
}
if(trys<10){
Serial.println(“ntp server update success”);
}
else{
Serial.println(“ntp server update failed”);
}
}

// Display the time if it has changed by more than a second.
if( now() != prevDisplay){
prevDisplay = now();
clockDisplay();
}
}

步骤7:12h vs. 24h 时间

 

起初,我用的是24小时的例程,所以下午一点显示为13.更多人喜欢12h的始终,带有AM/PM,所以我最终替换了例程。我再三考虑,加了一个可以选择格式的开关。

首先,我们需要读取开关来决定格式,然后我们需要基于读到的结果来控制代码。

开关需要5个引脚,以太网插板本身需要引脚4,10,11,12&13.

将开关连接在引脚5和地之间。你不需要上拉点入,因为我们将使用INPUT_PULLUP命令在Arduino内部实现。

这是目前需要修改的代码:

lcd.setCursor (0,0);
if (hour() < 10){
lcd.print(“0″); }
if (hour() > 12){
lcd.print(“0″);
lcd.print(hour()-12); } else {
lcd.print(hour()); }
lcd.print(“:”);
if (minute() < 10){
lcd.print(“0″); }
lcd.print(minute());
lcd.print(“:”);
if (second() < 10){
lcd.print(“0″); }
lcd.print(second());
if (hour() > 12){
lcd.print(” PM”); }
else {
lcd.print(” AM”); }

Here is how the new code with the option of switching back and forth would look like:

//12h_24h (at top of sketch before void setup
int timeFormatPin = 5;   // switch connected to digital pin 5
int timeFormatVal= 0;     // variable to store the read value

//put in void setup replaceing the original code listed above

lcd.setCursor (0,0);
if (hour() < 10){
lcd.print(“0″); }

//12h/24h
pinMode(timeFormatPin, INPUT_PULLUP);      // sets the digital pin 5 as input and activates pull up resistor
timeFormatVal= digitalRead(timeFormatPin);   // read the input pin
if (timeFormatVal == 1) {
lcd.print(hour());
} else {
if (hour() > 12){
lcd.print(“0″);
lcd.print(hour()-12); } else {
lcd.print(hour()); }
}

lcd.print(“:”);
if (minute() < 10){
lcd.print(“0″); }
lcd.print(minute());
lcd.print(“:”);
if (second() < 10){
lcd.print(“0″); }
lcd.print(second());
if (timeFormatVal == 1){
lcd.print(“ 24″);
} else {
if (hour() > 12){
lcd.print(” PM”); }
else {
lcd.print(” AM”); }
}

步骤8:标准与夏令时间

标准与夏令时间
最初当我构建这个草案,我们在夏令时GMT -4。当我们切换回标准时间(GMT -5)时,时钟代码必须被编辑和重新上传,所以添加一个开关来解决这个问题。

首先,我们需要读取一个开关来确定格式,然后我们需要根据读取的结果切换一些代码。

我们将使用针6作为开关,因为Ethernet Shield本身使用引脚4,10,11,12和13。

在引脚6和接地之间连接开关。您不需要上拉电阻,因为我们将在arduino中使用内置的一个INPUT_PULLUP命令。

要使之工作,你需要重置或重启你的Arduino之间的变化,因为开关代码不在void循环。

这里是受影响的代码:

/* Set this to the offset (in seconds) to your local time
This example is GMT – 4 */
const long timeZoneOffset = -14400L;

change to /* Set this to the offset (in seconds) to your local time
This example is GMT – 4 */
long timeZoneOffset;

add this before void setup:

//DST Switch
int dstPin = 6;   // switch connected to digital pin 5
int dstVal= 0;     // variable to store the read value

and change out the whole int getTimeAndDate() function with the code below:

// Do not alter this function, it is used by the system
int getTimeAndDate() {

// Time zone switch

pinMode(dstPin, INPUT_PULLUP);      // sets the digital pin 6 as input and activates pull up resistor
dstVal= digitalRead(dstPin);   // read the input pin
if (dstVal == 1) {
timeZoneOffset = -14400L;
} else {
timeZoneOffset = -18000L;
}

int flag=0;
Udp.begin(localPort);
sendNTPpacket(timeServer);
delay(1000);
if (Udp.parsePacket()){
Udp.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer
unsigned long highWord, lowWord, epoch;
highWord = word(packetBuffer[40], packetBuffer[41]);
lowWord = word(packetBuffer[42], packetBuffer[43]);
epoch = highWord << 16 | lowWord;
epoch = epoch – 2208988800 + timeZoneOffset;
flag=1;
setTime(epoch);
ntpLastUpdate = now();
}
return flag;
}

步骤9:未来升级

  • 未来升级
    我在构想的升级:
    用于查找可用时间服务器的自动功能
    自动切换夏令时
    Alalrm时钟功能带有声音报警,逐渐变亮的灯光和/或继电器。
    RTC用于电源故障,无网络启动
    您要添加什么?

步骤10:WiFi充电版本

WiFi充电版本
此版本的Internet Clock使用WiFi而不是以太网,以及板载可充电锂离子电池。有一个电源开关,关闭时钟,它重新同步的时间与互联网上电。它会在电池电量低的时候通知您,并且只需插入USB电缆即可充电。

Adafruit RGB I2C LCD显示屏

Adafruit Powerboost Lipo Shield

Arduino WiFi Shield(退役,有更新的版本)

Arduino UNO

代码和库

Source: http://www.instructables.com/id/Arduino-Internet-Time-Client/