newlib: implement settimeofday, integrate LwIP SNTP, add SNTP example

This commit is contained in:
Ivan Grokhotkov 2016-11-03 12:46:46 +08:00
parent c534dedf2d
commit eb2c633cbf
9 changed files with 314 additions and 9 deletions

View File

@ -324,7 +324,7 @@ choice ESP32_TIME_SYSCALL
default ESP32_TIME_SYSCALL_USE_RTC_FRC1
help
This setting defines which hardware timers are used to
implement 'gettimeofday' function in C library.
implement 'gettimeofday' and 'time' functions in C library.
- If only FRC1 timer is used, gettimeofday will provide time at
microsecond resolution. Time will not be preserved when going
@ -336,8 +336,8 @@ choice ESP32_TIME_SYSCALL
deep sleep, but time will be measured at 6.(6) microsecond
resolution. Also the gettimeofday function itself may take
longer to run.
- If no timers are used, gettimeofday function return -1 and
set errno to ENOSYS.
- If no timers are used, gettimeofday and time functions
return -1 and set errno to ENOSYS.
config ESP32_TIME_SYSCALL_USE_RTC
bool "RTC"

View File

@ -24,6 +24,15 @@ config LWIP_SO_REUSE
Enabling this option allows binding to a port which remains in
TIME_WAIT.
config LWIP_DHCP_MAX_NTP_SERVERS
int "Maximum number of NTP servers"
default 1
range 1 16
help
Set maxumum number of NTP servers used by LwIP SNTP module.
First argument of sntp_setserver/sntp_setservername functions
is limited to this value.
endmenu

View File

@ -33,6 +33,8 @@
#define __LWIPOPTS_H__
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include "esp_task.h"
#include "sdkconfig.h"
@ -552,7 +554,22 @@ extern unsigned char misc_prof_get_tcp_snd_buf(void);
#define LWIP_NETCONN_FULLDUPLEX 1
#define LWIP_NETCONN_SEM_PER_THREAD 1
#define LWIP_DHCP_MAX_NTP_SERVERS CONFIG_LWIP_DHCP_MAX_NTP_SERVERS
#define LWIP_TIMEVAL_PRIVATE 0
#define SNTP_SET_SYSTEM_TIME_US(sec, us) \
do { \
struct timeval tv = { .tv_sec = sec, .tv_usec = us }; \
settimeofday(&tv, NULL); \
} while (0);
#define SNTP_GET_SYSTEM_TIME(sec, us) \
do { \
struct timeval tv = { .tv_sec = 0, .tv_usec = 0 }; \
gettimeofday(&tv, NULL); \
(sec) = tv.tv_sec; \
(us) = tv.tv_usec; \
} while (0);
#define SOC_SEND_LOG //printf

View File

@ -20,6 +20,7 @@
#include <sys/reent.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/lock.h>
#include "esp_attr.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
@ -54,6 +55,15 @@ static uint64_t get_rtc_time_us()
#endif // WITH_RTC
// time from Epoch to the first boot time
#ifdef WITH_RTC
static RTC_DATA_ATTR struct timeval s_boot_time;
#else
static struct timeval s_boot_time;
#endif
static _lock_t s_boot_time_lock;
#ifdef WITH_FRC1
#define FRC1_PRESCALER 16
#define FRC1_PRESCALER_CTL 2
@ -83,7 +93,6 @@ void esp_setup_time_syscalls()
s_microseconds = get_rtc_time_us();
#endif //WITH_RTC
// set up timer
WRITE_PERI_REG(FRC_TIMER_CTRL_REG(0), \
FRC_TIMER_AUTOLOAD | \
@ -112,12 +121,12 @@ clock_t IRAM_ATTR _times_r(struct _reent *r, struct tms *ptms)
return (clock_t) tv.tv_sec;
}
int IRAM_ATTR _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz)
static uint64_t get_time_since_boot()
{
(void) tz;
uint64_t microseconds = 0;
#ifdef WITH_FRC1
uint32_t timer_ticks_before = READ_PERI_REG(FRC_TIMER_COUNT_REG(0));
uint64_t microseconds = s_microseconds;
microseconds = s_microseconds;
uint32_t timer_ticks_after = READ_PERI_REG(FRC_TIMER_COUNT_REG(0));
if (timer_ticks_after > timer_ticks_before) {
// overflow happened at some point between getting
@ -127,13 +136,22 @@ int IRAM_ATTR _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz)
}
microseconds += (FRC_TIMER_LOAD_VALUE(0) - timer_ticks_after) / FRC1_TICKS_PER_US;
#elif defined(WITH_RTC)
uint64_t microseconds = get_rtc_time_us();
microseconds = get_rtc_time_us();
#endif
return microseconds;
}
int IRAM_ATTR _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz)
{
(void) tz;
#if defined( WITH_FRC1 ) || defined( WITH_RTC )
uint64_t microseconds = get_time_since_boot();
if (tv) {
tv->tv_sec = microseconds / 1000000;
_lock_acquire(&s_boot_time_lock);
microseconds += s_boot_time.tv_usec;
tv->tv_sec = s_boot_time.tv_sec + microseconds / 1000000;
tv->tv_usec = microseconds % 1000000;
_lock_release(&s_boot_time_lock);
}
return 0;
#else
@ -141,3 +159,24 @@ int IRAM_ATTR _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz)
return -1;
#endif // defined( WITH_FRC1 ) || defined( WITH_RTC )
}
int settimeofday(const struct timeval *tv, const struct timezone *tz)
{
(void) tz;
#if defined( WITH_FRC1 ) || defined( WITH_RTC )
if (tv) {
_lock_acquire(&s_boot_time_lock);
uint64_t now = ((uint64_t) tv->tv_sec) * 1000000LL + tv->tv_usec;
uint64_t since_boot = get_time_since_boot();
uint64_t boot_time = now - since_boot;
s_boot_time.tv_sec = boot_time / 1000000;
s_boot_time.tv_usec = boot_time % 1000000;
_lock_release(&s_boot_time_lock);
}
return 0;
#else
__errno_r(r) = ENOSYS;
return -1;
#endif
}

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := sntp
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,41 @@
# Example: using LwIP SNTP module and time functions
This example demonstrates the use of LwIP SNTP module to obtain time from Internet servers. See the README.md file in the upper level 'examples' directory for more information about examples.
## Obtaining time using LwIP SNTP module
When this example boots first time after ESP32 is reset, it connects to WiFi and obtains time using SNTP.
See `initialize_sntp` function for details.
## Timekeeping
Once time is synchronized, ESP32 will perform timekeeping using built-in timers.
- RTC clock is used to maintain accurate time when chip is in deep sleep mode
- FRC1 timer is used to provide time at microsecond accuracy when ESP32 is running.
Timekeeping using RTC timer is demonstrated in this example by going into deep sleep mode. After wake up, ESP32 will print current time without connecting to WiFi.
To use this functionality, make sure "Timers used for gettimeofday function" option in "ESP32-specific config" menu of menuconfig is set to "RTC and FRC1" or "RTC".
## Working with time
To get current time, [`gettimeofday`](http://man7.org/linux/man-pages/man2/gettimeofday.2.html) function may be used. Additionally the following [standard C library functions](http://en.cppreference.com/w/cpp/header/ctime) can be used to obtain time and manipulate it:
gettimeofday
time
asctime
clock
ctime
difftime
gmtime
localtime
mktime
strftime
To set time, [`settimeofday`](http://man7.org/linux/man-pages/man2/settimeofday.2.html) POSIX function can be used. It is used internally in LwIP SNTP library to set current time when response from NTP server is received.
## Timezones
To set local timezone, use [`setenv`](http://man7.org/linux/man-pages/man3/setenv.3.html) and [`tzset`](http://man7.org/linux/man-pages/man3/tzset.3.html) POSIX functions. First, call `setenv` to set `TZ` environment variable to the correct value depending on device location. Format of the time string is described in [libc documentation](https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html). Next, call `tzset` to update C library runtime data for the new time zone. Once these steps are done, `localtime` function will return correct local time, taking time zone offset and daylight saving time into account.

View File

@ -0,0 +1,17 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "myssid"
help
WiFi password (WPA or WPA2) for the example to use.
Can be left blank if the network has no security set.
endmenu

View File

@ -0,0 +1,10 @@
#
# Main Makefile. This is basically the same as a component makefile.
#
# This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default,
# this will take the sources in the src/ directory, compile them and link them into
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#
include $(IDF_PATH)/make/component_common.mk

View File

@ -0,0 +1,163 @@
/* LwIP SNTP example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_deepsleep.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "apps/sntp/sntp.h"
/* The examples use simple WiFi configuration that you can set via
'make menuconfig'.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;
/* The event group allows multiple bits for each event,
but we only care about one event - are we connected
to the AP with an IP? */
const int CONNECTED_BIT = BIT0;
static const char *TAG = "example";
/* Variable holding number of times ESP32 restarted since first boot.
* It is placed into RTC memory using RTC_DATA_ATTR and
* maintains its value when ESP32 wakes from deep sleep.
*/
RTC_DATA_ATTR static int boot_count = 0;
static void obtain_time(void);
static void initialize_sntp(void);
static void initialise_wifi(void);
static esp_err_t event_handler(void *ctx, system_event_t *event);
void app_main()
{
++boot_count;
ESP_LOGI(TAG, "Boot count: %d", boot_count);
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
// Is time set? If not, tm_year will be (1970 - 1900).
if (timeinfo.tm_year < (2016 - 1900)) {
ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
obtain_time();
// update 'now' variable with current time
time(&now);
}
char strftime_buf[64];
// Set timezone to Eastern Standard Time and print local time
setenv("TZ", "EST5EDT,M3.2.0/2,M11.1.0", 1);
tzset();
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "The current date/time in New York is: %s", strftime_buf);
// Set timezone to China Standard Time
setenv("TZ", "CST-8CDT-9,M4.2.0/2,M9.2.0/3", 1);
tzset();
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf);
const int deep_sleep_sec = 10;
ESP_LOGI(TAG, "Entering deep sleep for %d seconds", deep_sleep_sec);
system_deep_sleep(1000000LL * deep_sleep_sec);
}
static void obtain_time(void)
{
nvs_flash_init();
system_init();
initialise_wifi();
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
false, true, portMAX_DELAY);
initialize_sntp();
// wait for time to be set
time_t now = 0;
struct tm timeinfo = { 0 };
int retry = 0;
const int retry_count = 10;
while(timeinfo.tm_year < (2016 - 1900) && ++retry < retry_count) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
vTaskDelay(2000 / portTICK_PERIOD_MS);
time(&now);
localtime_r(&now, &timeinfo);
}
}
static void initialize_sntp(void)
{
ESP_LOGI(TAG, "Initializing SNTP");
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, "pool.ntp.org");
sntp_init();
}
static void initialise_wifi(void)
{
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_WIFI_SSID,
.password = EXAMPLE_WIFI_PASS,
},
};
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_start() );
}
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
/* This is a workaround as ESP32 WiFi libs don't currently
auto-reassociate. */
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
break;
default:
break;
}
return ESP_OK;
}