At the upper stack levels uIP brings some protocol and application support. weAutSys adapted and uses some of those (DHCP, ARP, DNS) as well as implementing some other protocols (like NTP, Telnet server).
weAutSys' NTP client is best used with above said DHCP client, which can get one or two NTP server addresses. Any Windows or appropriate Linux server on the network segment can take the role of the time server for all (weAut_01) automation modules there.
DHCP and NTP may very well be on the same server machine.
As the recommended configuration is to have a separated private LAN segment for all automation modules plus their supporting central servers some of the complexities of the NTP protocol (with the RFC's permission) are dismissed.
Complexities dismissed are, for example, expensive filtering and selection algorithms which could deal with malicious servers among others. It is assumed Bycantine servers being kept away from above said automation LAN by other means. And of course, all principal limitations of NTP synchronisation, like unavoidable offsets due to network round trip and software time stamping asymmetries etc., do apply also here.
On the other hand weAutSys' NTP client implementation is not as simplistic as SNTP. It uses the obvious NTP difference formulas and sets the date / time (hard) for big differences. Relative times and timers are not affected by those settings ("jumps"), but functions and timers dependent on absolute time and date may be.
Such hard setting of date and time will occur once after reset or power up as soon as a connection to a NTP server is established.
Small differences will be caught up by slightly trimming the oscillator responsible for the milliseconds tick. As long as the network and above said NTP server behave well, the control strategy implemented will make this tick exact in the median. And it will avoid all further (hard) date / time settings. The only exception then remaining will necessarily be leap seconds and DST shifts.
Relative milliseconds and seconds timers will (in the median) also get the NTP servers accuracy. Depending on atomic time, NTP time and related frequency accuracy is better than weAut_01 quartz oscillator's — which is quite good by the way. But beware, there are reports of surprisingly bad (black sheep) NTP servers around — using (indirectly) on of them is worse than having none. Just check.
Files | |
file | ntp.h |
weAutSys' system calls and types for network time services | |
Data Structures | |
struct | ntpMess_t |
NTP message type. More... | |
struct | ntpState_t |
Structure for NTP (client) application state. More... | |
struct | ntpTimestamp_t |
NTP time stamp type. More... | |
Defines | |
#define | FLAGS_LI_MASK |
leap indicator bits in ntpMess_t::flags | |
#define | FLAGS_MODE_MASK |
mode bits in ntpMess_t::flags | |
#define | FLAGS_VERSION_MASK |
version bits in ntpMess_t::flags | |
#define | LI_59SEC |
leap indicator 2 : minute = 59s | |
#define | LI_61SEC |
leap indicator 1 : minute = 61s | |
#define | LI_ALARM |
leap indicator 3 : alarm (clock not synchronized) | |
#define | LI_NOWARN |
leap indicator 0 : no warning | |
#define | NTP_ADJUSTAT_DIRMSK |
last adjustment direction mask | |
#define | NTP_ADJUSTAT_MINUS |
last adjustment negative | |
#define | NTP_ADJUSTAT_PLUS |
last adjustment positive | |
#define | NTP_ADJUSTAT_PRV_MINUS |
previous (not last) adjustment negative | |
#define | NTP_ADJUSTAT_PRV_MSK |
previous (not last) adjustment mask | |
#define | NTP_ADJUSTAT_PRV_PLUS |
lprevious (not last) adjustment positive | |
#define | NTP_ADJUSTAT_RNG_1S |
last adjustment range +1 second | |
#define | NTP_ADJUSTAT_RNG_HM |
last adjustment range milliseconds | |
#define | NTP_ADJUSTAT_RNG_HS |
last adjustment range seconds | |
#define | NTP_ADJUSTAT_RNG_LM |
last adjustment range low milliseconds | |
#define | NTP_ADJUSTAT_RNGMSK |
last adjustment range mask | |
#define | NTP_BROADCAST |
NTP mode broadcast (5) | |
#define | NTP_CLIENT |
NTP mode client (3) | |
#define | NTP_FRACT_DROP |
Fraction byte significance. | |
#define | NTP_PORT |
The well known NTP port. | |
#define | NTP_SERVER |
NTP mode server (4) | |
#define | NTP_STATE_CNETAB 0xE0 |
connection established | |
#define | NTP_STATE_CNPROB 0xF0 |
connection problematic | |
#define | NTP_STATE_CONN1 1 |
Server known, port connected to server 1. | |
#define | NTP_STATE_CONN2 2 |
Server known, port connected to server 2. | |
#define | NTP_STATE_CONTSY 0xC0 |
continuous synchronisation | |
#define | NTP_STATE_OPMSK |
Operation (bits) mask. | |
#define | NTP_STATE_RESET 0 |
No known NTP server, no NTP client operation. | |
#define | NTP_STATE_SEPROB 0xB0 |
server answer problematic (bogus, alarm) | |
#define | NTP_STATE_SRVMSK |
Server (bits) mask. | |
#define | NTP_STATE_TRYREQ 0x10 |
try (first) request | |
#define | NTP_STATE_W4REPL 8 |
flag: waiting for server's reply | |
#define | NTP_VERSION |
current version (4) within FLAGS_VERSION_MASK | |
#define | ntpAsksPrio() |
NTP asks for priority. | |
Functions | |
void | adjustNTPtime308UTC (struct ntpTimestamp_t *ntpTimeDif) |
Adjust the actual (system) time by a NTP time stamp difference. | |
ptfnct_t | ntpAppcall (void) |
Handle NTP (server) events. | |
void | ntpInit (uint8_t pref2) |
Initialise the NTP (client) | |
void | ntpReset (void) |
Reset the NTP (client) to initial state. | |
void | ntpTimestampDif (struct ntpTimestamp_t *result, struct ntpTimestamp_t *a, struct ntpTimestamp_t *b) |
Difference of two NTP time stamps. | |
void | ntpTimestampHalf (struct ntpTimestamp_t *result) __attribute__((pure)) |
Half a NTP time stamp value. | |
void | ntpTimestampSum (struct ntpTimestamp_t *result, struct ntpTimestamp_t *a, struct ntpTimestamp_t *b) |
Sum of two NTP time stamps. | |
void | ntpTimestampToMillies (uint16_t *ms, struct ntpTimestamp_t *ntpStamp) |
Milliseconds from a NTP time stamp's fraction. | |
void | setNTPstampAct (struct ntpTimestamp_t *ntpTimestamp) |
Set a NTP time stamp to actual time. | |
Variables | |
struct ntpState_t | ntpState |
the NTP state |
#define NTP_PORT |
The well known NTP port.
NTP normally uses UDP port 123.
#define NTP_FRACT_DROP |
Fraction byte significance.
As said NTP time stamp's fraction has four bytes allowing 2**-32 = ~0.23ns resolution. That is far beyond any practical use for e.g. ATmel ATmega1284P based automation modules, like weAut_01 with 20MHz processor clock (50ns period) at best.
Hence this value determines the number of bytes being ignored for all 64 bit NTP time stamp operations.
Range: 0 (full 64 bit arithmetic) .. 3 (fraction is 8 bit only)
Recommended / default: 2 (16 bit fraction, sub-ms resolution)
The recommended value 2 is a good choice also in the light of most NTP servers. Observations of Windows 2008 R2 as NTP server showed the least 16 fraction bits being always the same (rubbish ?) value no matter the circumstances.
NTP_FRACT_DROP
2 reduces ntpTimestampSum, ntpTimestampDif and ntpTimestampHalf() from 64 bit to 48 bit arithmetic (saving ten / six cycles). NTP_FRACT_DROP
2 also fits well to ntpTimestampToMillies() and setNTPstampAct().
#define ntpAsksPrio | ( | ) |
NTP asks for priority.
This expression is true if the NTP client sent a request and waits for the response. Postponing the reaction to this response by using needed resources (NIC, SPI) or having long running thread steps (by SMC block operations e.g.) will detriment the synchronisation accuracy.
As NTP synchronisations are quite seldom (about every minute in good sync state) and turn-around for (recommended) local NTP servers is quite fast it should be feasible for all others to respect this request for priority.
void ntpTimestampSum | ( | struct ntpTimestamp_t * | result, |
struct ntpTimestamp_t * | a, | ||
struct ntpTimestamp_t * | b | ||
) |
Sum of two NTP time stamps.
This function calculates *result = *a + *b;
No pointer must be null, but two or three of them might point to the same ntpTimestamp_t structure.
ntpTimestampSum, ntpTimestampDif and ntpTimestampHalf are optimised implementations for 64 bit wrong endian (i.e. network byte order).
The former two take 74 cycles and the latter 43. This values are for NTP_FRACT_DROP 0, the recommended value 2 makes all a bit faster.
a | points to first operand (not NULL) |
b | points to second operand (not NULL) |
result | points to result structure (not NULL) |
void ntpTimestampDif | ( | struct ntpTimestamp_t * | result, |
struct ntpTimestamp_t * | a, | ||
struct ntpTimestamp_t * | b | ||
) |
Difference of two NTP time stamps.
This function calculates *result = *a - *b;
No pointer must be null, but two of them might point to the same ntpTimestamp_t structure.
ntpTimestampSum, ntpTimestampDif and ntpTimestampHalf are optimised implementations for 64 bit big endian.
a | points to first operand (not NULL) |
b | points to second operand (not NULL) |
result | points to result structure (not NULL) |
void ntpTimestampHalf | ( | struct ntpTimestamp_t * | result | ) |
Half a NTP time stamp value.
This function calculates *result = *result / 2
The division by 2 is performed for a signed (two complement) 64 bit number. This also gives correct results for large differences in modulo 2**64 bit arithmetic, but will of course fail on very large unsigned numbers >= 2**63.
ntpTimestampSum, ntpTimestampDif and ntpTimestampHalf are optimised implementations for 64 bit big endian.
result | points to operand and result structure (not NULL) |
void ntpTimestampToMillies | ( | uint16_t * | ms, |
struct ntpTimestamp_t * | ntpStamp | ||
) |
Milliseconds from a NTP time stamp's fraction.
This function converts a NTP time stamp's fraction value (32 bit) into milliseconds in the range 0 .. 999 (16 bit).
The fraction is expected in (wrong) network endianess while the result will be set in the correct byte order.
The implementation is (inline) ASM optimized. It sacrifices a tiny bit of exactness in favor of a slow RISC processor with only byte operations. The effect is the 0 .. 999ms being mapped to 0..992ms.
The assumption behind is the worst case 1% of a second error a being far within the NTP synchronisation abilities of an ATmega-uIP-ENC trio.
Round trip times of 50ms .. 80ms with a Windows 2008 R2 time server in a local network with some switches in between suggest the un-symmetry under these near ideal conditions being at least some milliseconds. Hence the above assumption seems quite adequate.
ntpStamp | structure pointed to has fraction in network byte order; never null! Using a pointer for this 16 bit result instead of a return value is just a work around a GCC inline ASM bug. |
ms | points to the result variable where to put value.fraction converted to ms (0 .. 999) to; never null |
void setNTPstampAct | ( | struct ntpTimestamp_t * | ntpTimestamp | ) |
Set a NTP time stamp to actual time.
This function sets the .seconds and
.fraction of
ntpTimestamp
to the actual UTC system time (secTime308UTC(), msAbsClockCount).
On .fraction all but the upper 10 bits are set to zero.
The ms to fraction algorithm implementation is (ASM) optimised for the 8bit AVR RISC. It sacrifices a tiny bit of exactness in favor of great CPU usage savings. The effect is the 0 .. 999ms of msAbsClockCount being mapped to effectively 0..992ms in the NTP stamp fraction.
ntpTimestamp | points to the stamp to be set (never null) |
void adjustNTPtime308UTC | ( | struct ntpTimestamp_t * | ntpTimeDif | ) |
Adjust the actual (system) time by a NTP time stamp difference.
This function adjusts the internal date and time (secTime308UTC()) by an amount given as a a NTP time stamp difference to theactual time in s, ms.
Big (>=2**31 in the seconds part) differences will quite naturally act as negative in the sense of second based timing's (and NTP's) modulo 2**32 arithmetic.
This function should be used for any absolute date time adjustments caused by NTP client functions (or may be also other time sources).
The action taken and the flags set in ntpState .lastAdjust depend on the (absolute) difference amount
ntpTimeDif | the adjustment difference in seconds,fraction; big values act as negative in the sense of seconds modulo 2**32 arithmetic |
void ntpInit | ( | uint8_t | pref2 | ) |
Initialise the NTP (client)
This function initialises the NTP structures and (protothread) state machine. The protocol is not started by this function; the work is done by later calls of ntpAppcall(). There is no need to call ntpReset() before.
If "be NTP client" is not set in curIpConf nothing will be done except setting the state to NTP_STATE_RESET.
If the parameter pref2
is true (not 0) the second NTP server in the current IP configuration will be used, if set there. Successful bind to a set NTP server will set the (NTP client) state to NTP_STATE_CONN1 respectively NTP_STATE_CONN2.
pref2 | if true use second NTP server (if available) |
void ntpReset | ( | void | ) |
Reset the NTP (client) to initial state.
This function initialises the NTP structures and (protothread) state machine. This function resets all NTPP state to initial. In contrast to ntpInit no trial to connect or bind to NTP ports or servers. The protocol is not (not even indirectly) started.
ptfnct_t ntpAppcall | ( | void | ) |
Handle NTP (server) events.
This function handles NTP events. And it is a (as a protothread) the NTP client state machine. It has to be called in the udp_appcall in a manner like
const uint16_t remPort = convert16endian(uip_udp_conn->rport); if(remPort == NTP_PORT) { // NTP protocol ntpAppcall(); return; } // NTP protocol