Skip to content

Commit 472df6f

Browse files
feat: use RTC for tracking time (#2)
Within this commit, we enable real time counter in ESP32 to reduce the drift and enable future deep-sleep implementations. Also, this commit patches some logs to reduce the amount of logs and increases the stack for debug purposes.
1 parent 6c3a8a6 commit 472df6f

File tree

6 files changed

+87
-55
lines changed

6 files changed

+87
-55
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
build/*
22
.vscode/*
3-
.DS_Store
3+
.DS_Store
4+
.cache/*

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
This is a smart-watch project based on ESP32, LVGL and Zephyr RTOS. I started it as a side project to learn about Zephyr, BLE and LVGL. As you can imagine, it was a big kick to learn about many stuff at oonce. Now, I'm proud to present you. Feel free to contribute and/or contact to me!
44

55
### Features
6+
- Real-Time Counter to Track the Time
67
- LVGL for UI and Graphics Rendering
78
- BLE Current Time Service (GATT) for Time Synchronization
89
- BLE Device Information Service (DIS) for Device Metadata

boards/esp32.overlay

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
&timer0 {
22
status = "okay";
3+
};
4+
5+
&rtc_timer {
6+
status = "okay";
37
};

prj.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ CONFIG_LVGL=y
1616
CONFIG_LV_FONT_MONTSERRAT_46=y
1717
CONFIG_LV_FONT_MONTSERRAT_18=y
1818
# Important for LVGL to work.
19-
CONFIG_MAIN_STACK_SIZE=4096
19+
CONFIG_MAIN_STACK_SIZE=8192
2020
# CONFIG_LV_MEM_CUSTOM=y
2121
CONFIG_LV_Z_MEM_POOL_SIZE=16384
2222

src/bluetooth/infrastructure.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ uint8_t enable_bluetooth_and_start_advertisement() {
4141
return err;
4242
}
4343

44-
LOG_INF("Bluetooth initialized.");
44+
LOG_DBG("Bluetooth initialized.");
4545

4646
if (IS_ENABLED(CONFIG_SETTINGS)) {
4747
settings_load();
@@ -53,7 +53,7 @@ uint8_t enable_bluetooth_and_start_advertisement() {
5353
return err;
5454
}
5555

56-
LOG_INF("Advertising successfully started.");
56+
LOG_DBG("Advertising successfully started.");
5757
return 0;
5858
}
5959

@@ -66,14 +66,14 @@ uint8_t disable_bluetooth_and_stop_advertisement() {
6666
return err;
6767
}
6868

69-
LOG_INF("Advertising successfully stopped.");
69+
LOG_DBG("Advertising successfully stopped.");
7070

7171
err = bt_disable();
7272
if (err) {
7373
LOG_ERR("Bluetooth failed to disable (err %d).", err);
7474
return err;
7575
}
7676

77-
LOG_INF("Bluetooth disabled.");
77+
LOG_DBG("Bluetooth disabled.");
7878
return 0;
7979
}

src/main.c

Lines changed: 75 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
/**
2-
* @file src/main.c
3-
*
4-
* @brief Main file for the application.
5-
*/
6-
71
#include <stdio.h>
82
#include <string.h>
93

@@ -19,6 +13,7 @@
1913
#include <zephyr/drivers/gpio.h>
2014
#include <zephyr/drivers/pwm.h>
2115
#include <zephyr/bluetooth/bluetooth.h>
16+
#include <zephyr/drivers/counter.h>
2217

2318
#include "display/display.h"
2419
#include "display/screens/home/home.h"
@@ -28,17 +23,15 @@
2823
#include "bluetooth/services/current_time_service.h"
2924

3025
// Define the logger.
31-
LOG_MODULE_REGISTER(ZephyrWatch, LOG_LEVEL_DBG);
26+
LOG_MODULE_REGISTER(ZephyrWatch, LOG_LEVEL_INF);
3227

3328
// Global values to hold time.
3429
uint32_t unix_time = 1748554674;
3530
int8_t utc_zone = +2;
3631
const char* weekdays[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
3732

3833
// Define the timer callbacks' prototypes.
39-
void update_unix_time_callback(struct k_timer *timer);
4034
void update_clock_view_callback(struct k_timer *timer);
41-
void update_date_day_view_callback(struct k_timer *timer);
4235
// Define the work queues' prototypes.
4336
void clock_update_worker(struct k_work *work);
4437
void date_day_update_worker(struct k_work *work);
@@ -51,9 +44,30 @@ static struct k_work clock_update_work;
5144
static struct k_work date_day_update_work;
5245

5346
// Define timers.
54-
K_TIMER_DEFINE(unix_time_timer, update_unix_time_callback, NULL);
47+
// K_TIMER_DEFINE(unix_time_timer, update_unix_time_callback, NULL);
5548
K_TIMER_DEFINE(clock_view_timer, update_clock_view_callback, NULL);
56-
K_TIMER_DEFINE(date_day_view_timer, update_date_day_view_callback, NULL);
49+
50+
// Create an alarm for 1 second.
51+
#define ALARM_INTERVAL_US 1000000
52+
#define ALARM_CHANNEL_ID 0
53+
54+
55+
void rtc_counter_isr(
56+
const struct device *dev,
57+
uint8_t channel_id,
58+
uint32_t ticks,
59+
void *user_data)
60+
{
61+
// Cast alarm config from user data.
62+
struct counter_alarm_cfg *alarm_cfg = user_data;
63+
64+
// Reset alarm
65+
alarm_cfg->ticks = counter_us_to_ticks(dev, ALARM_INTERVAL_US);
66+
counter_set_channel_alarm(dev, ALARM_CHANNEL_ID, alarm_cfg);
67+
68+
// Update the second.
69+
unix_time++;
70+
}
5771

5872

5973
int main(void) {
@@ -65,61 +79,54 @@ int main(void) {
6579
LOG_ERR("Cannot create device twin instance.");
6680
return 0;
6781
}
68-
LOG_INF("Device twin instance created successfully.");
69-
70-
// Start the timer to track the real time.
71-
k_timer_start(&unix_time_timer, K_MSEC(60), K_SECONDS(1));
72-
k_timer_start(&clock_view_timer, K_MSEC(60), K_SECONDS(30));
73-
k_timer_start(&date_day_view_timer, K_MSEC(60), K_MINUTES(1));
74-
LOG_INF("Timers started successfully.");
82+
LOG_DBG("Device twin instance created successfully.");
7583

7684
// Check if the display device is ready.
7785
const struct device *display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
7886
if (!device_is_ready(display_dev)) {
7987
LOG_ERR("Display device is not ready, exiting...");
8088
return 0;
8189
}
82-
LOG_INF("Display device is ready.");
90+
LOG_DBG("Display device is ready.");
8391

8492
const struct pwm_dt_spec backlight = PWM_DT_SPEC_GET_BY_IDX(DT_NODELABEL(pwm_lcd0), 0);
8593
if (!pwm_is_ready_dt(&backlight)) {
8694
LOG_ERR("PWM device is not ready, exiting...");
8795
return 0;
8896
}
89-
LOG_INF("PWM device is ready.");
97+
LOG_DBG("PWM device is ready.");
9098

9199
// Initialize the PWM device.
92100
ret = pwm_set_dt(&backlight, 500, 250);
93101
if (ret < 0) {
94102
LOG_ERR("Failed to set PWM pulse, exiting...");
95103
return 0;
96104
}
97-
LOG_INF("PWM pulse for LCD backlight set.");
105+
LOG_DBG("PWM pulse for LCD backlight set.");
98106

99107
// Initialize the display device with initial GUI.
100108
display_init();
101-
LOG_INF("UI initialized.");
109+
LOG_DBG("UI initialized.");
102110

103111
// Start the UI work queue.
104112
k_work_queue_start(&ui_work_q, ui_stack_area, K_THREAD_STACK_SIZEOF(ui_stack_area),
105113
K_PRIO_PREEMPT(5), NULL);
106-
LOG_INF("UI work queue started.");
114+
LOG_DBG("UI work queue started.");
107115

108116
// Initialize the work items.
109117
k_work_init(&clock_update_work, clock_update_worker);
110118
k_work_init(&date_day_update_work, date_day_update_worker);
111119

112-
// Start timers after work queue is ready - reduced frequency to prevent queue overflow
113-
k_timer_start(&unix_time_timer, K_MSEC(1000), K_SECONDS(1));
114-
k_timer_start(&clock_view_timer, K_MSEC(2000), K_SECONDS(30));
115-
k_timer_start(&date_day_view_timer, K_MSEC(3000), K_MINUTES(1));
116-
LOG_INF("Timers are set.");
120+
// Start timers after work queue is ready - reduced frequency to prevent queue overflow.
121+
k_timer_start(&clock_view_timer, K_MSEC(2000), K_SECONDS(10));
122+
k_work_submit_to_queue(&ui_work_q, &date_day_update_work);
123+
LOG_DBG("Timers are set.");
117124

118125
// Initialize LVGL first and let UI stabilize
119126
lv_task_handler();
120127
display_blanking_off(display_dev);
121128

122-
// Give the system more time to stabilize before initializing Bluetooth
129+
// Give the system more time to stabilize before initializing Bluetooth.
123130
k_sleep(K_SECONDS(2));
124131

125132
// Initialize the Bluetooth stack.
@@ -128,7 +135,36 @@ int main(void) {
128135
LOG_ERR("Bluetooth init failed (ret %d).", ret);
129136
return ret;
130137
}
131-
LOG_INF("Bluetooth subsytem is initialized.");
138+
LOG_INF("Started to advertise with Bluetooth.");
139+
140+
// Set the real time counter to trigger an callback every second.
141+
const struct device *real_time_counter = DEVICE_DT_GET(DT_NODELABEL(rtc_timer));
142+
if (!device_is_ready(real_time_counter)) {
143+
LOG_ERR("Real time counter device is not ready.");
144+
return -ENODEV;
145+
}
146+
LOG_DBG("Real time counter device is ready.");
147+
148+
// Configure real time counter to track tine.
149+
ret = counter_start(real_time_counter);
150+
if (ret) {
151+
LOG_ERR("Failed to start real time counter (ret %d).", ret);
152+
return ret;
153+
}
154+
LOG_DBG("Real time counter started successfully.");
155+
156+
struct counter_alarm_cfg alarm_cfg = {
157+
.flags = 0,
158+
.ticks = counter_us_to_ticks(real_time_counter, ALARM_INTERVAL_US),
159+
.callback = rtc_counter_isr,
160+
.user_data = &alarm_cfg,
161+
};
162+
ret = counter_set_channel_alarm(real_time_counter, ALARM_CHANNEL_ID, &alarm_cfg);
163+
if (ret) {
164+
LOG_ERR("Failed to set channel alarm (ret %d).", ret);
165+
return ret;
166+
}
167+
LOG_DBG("Channel alarm set successfully.");
132168

133169
while (1) {
134170
lv_task_handler();
@@ -141,35 +177,18 @@ int main(void) {
141177
* It sends an event to the UI work queue to update the clock view.
142178
*/
143179
void update_clock_view_callback(struct k_timer *timer) {
180+
LOG_DBG("Pushing clock_update_work work to ui_work_q.");
144181
k_work_submit_to_queue(&ui_work_q, &clock_update_work);
145182
}
146183

147-
/* UPDATE_DATE_DAY_VIEW_CALLBACK
148-
* This function is called by the timer to update the date and day views.
149-
* It sends an event to the UI work queue to update the date and day views.
150-
*/
151-
void update_date_day_view_callback(struct k_timer *timer) {
152-
k_work_submit_to_queue(&ui_work_q, &date_day_update_work);
153-
}
154-
155-
/* UPDATE_UNIX_TIME_CALLBACK
156-
* This timer callback function runs every second to update
157-
* the value inside the unix_time to track the real time.
158-
* It is neccessary to use timers instead of configuring the
159-
* RTC because the PoC board does not have RTC support in Zephyr.
160-
*/
161-
void update_unix_time_callback(struct k_timer *timer) {
162-
unix_time += 1;
163-
}
164-
165184
/* UPDATE_GLOBAL_UNIX_TIME
166185
* Function to update the global unix time from external sources
167186
* (like Bluetooth CTS). This function will be called by the external
168187
* sources.
169188
*/
170189
void update_global_unix_time(uint32_t unix_timestamp) {
171190
unix_time = unix_timestamp;
172-
LOG_INF("Global UNIX time updated to: %u", unix_time);
191+
LOG_INF("unix_time is updated to: %u", unix_time);
173192

174193
// Only submit if not already pending.
175194
if (!k_work_is_pending(&clock_update_work)) {
@@ -187,6 +206,7 @@ void update_global_unix_time(uint32_t unix_timestamp) {
187206
* calls the home screen set clock function.
188207
*/
189208
void clock_update_worker(struct k_work *work) {
209+
LOG_DBG("unix_time=%u", unix_time);
190210
// Get the device twin to find UTC zone.
191211
device_twin_t* device_twin = get_device_twin_instance();
192212

@@ -199,6 +219,12 @@ void clock_update_worker(struct k_work *work) {
199219
if (ret != 0) {
200220
LOG_ERR("Failed to update the clock view.");
201221
}
222+
223+
// Send a date day update worker if 00:00.
224+
if (device_twin->current_time.hour == 0 && device_twin->current_time.minute == 0) {
225+
k_work_submit_to_queue(&ui_work_q, &date_day_update_work);
226+
LOG_DBG("Date and day update worker submitted to queue.");
227+
}
202228
}
203229

204230
/* DATE_DAY_UPDATE_WORKER

0 commit comments

Comments
 (0)