aboutsummaryrefslogtreecommitdiff
path: root/application.c
blob: e1b8171f5cf8f7b181c22fcfacbf6f4a9b77b0e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
/** STM32F4 application example
 *  @file
 *  @author King Kévin <kingkevin@cuvoodoo.info>
 *  @copyright SPDX-License-Identifier: GPL-3.0-or-later
 *  @date 2016-2021
 */

/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // standard utilities
#include <string.h> // string utilities
#include <time.h> // date/time utilities
#include <ctype.h> // utilities to check chars

/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/scb.h> // vector table definition
#include <libopencm3/cm3/nvic.h> // interrupt utilities
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/exti.h> // external interrupt utilities
#include <libopencm3/stm32/rtc.h> // real time clock utilities
#include <libopencm3/stm32/iwdg.h> // independent watchdog utilities
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities

/* own libraries */
#include "global.h" // board definitions
#include "print.h" // printing utilities
#include "uart.h" // USART utilities
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "terminal.h" // handle the terminal interface
#include "menu.h" // menu utilities

/** watchdog period in ms */
#define WATCHDOG_PERIOD 10000

/** wakeup frequency (i.e. least number of times per second to  perform the main loop) */
#define WAKEUP_FREQ 16

/** @defgroup main_flags flag set in interrupts to be processed in main task
 *  @{
 */
static volatile bool wakeup_flag = false; /**< flag set when wakeup timer triggered */
static volatile bool second_flag = false; /**< flag set when a second passed */
/** @} */

/** number of seconds since boot */
static uint32_t boot_time = 0;

size_t putc(char c)
{
	size_t length = 0; // number of characters printed
	static char last_c = 0; // to remember on which character we last sent
	if ('\n' == c) { // send carriage return (CR) + line feed (LF) newline for each LF
		if ('\r' != last_c) { // CR has not already been sent
			uart_putchar_nonblocking('\r'); // send CR over USART
			usb_cdcacm_putchar('\r'); // send CR over USB
			length++; // remember we printed 1 character
		}
	}
	uart_putchar_nonblocking(c); // send byte over USART
	usb_cdcacm_putchar(c); // send byte over USB
	length++; // remember we printed 1 character
	last_c = c; // remember last character
	return length; // return number of characters printed
}

// only print when debug is enabled
#if DEBUG
#define puts_debug(x) puts(x)
#else
#define puts_debug(x) {}
#endif

/** display available commands
 *  @param[in] argument no argument required
 */
static void command_help(void* argument);

/** show software and hardware version
 *  @param[in] argument no argument required
 */
static void command_version(void* argument)
{
	(void)argument; // we won't use the argument
	printf("firmware date: %04u-%02u-%02u\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date
	printf("device serial: %08x%08x%08x\n", DESIG_UNIQUE_ID2, DESIG_UNIQUE_ID1, DESIG_UNIQUE_ID0); // show complete serial (different than the one used for USB)
}

/** convert RTC date/time to number of seconds
 *  @return number of seconds since 2000-01-01 00:00:00
 *  @warning for simplicity I consider every month to have 31 days
 */
static uint32_t rtc_to_seconds(void)
{
	rtc_wait_for_synchro(); // wait until date/time is synchronised
	const uint8_t year = ((RTC_DR >> RTC_DR_YT_SHIFT) & RTC_DR_YT_MASK) * 10 + ((RTC_DR >> RTC_DR_YU_SHIFT) & RTC_DR_YU_MASK); // get year
	uint8_t month = ((RTC_DR >> RTC_DR_MT_SHIFT) & RTC_DR_MT_MASK) * 10 + ((RTC_DR >> RTC_DR_MU_SHIFT) & RTC_DR_MU_MASK); // get month
	if (month > 0) { // month has been initialized, but starts with 1
		month--; // fix for calculation
	}
	uint8_t day = ((RTC_DR >> RTC_DR_DT_SHIFT) & RTC_DR_DT_MASK) * 10 + ((RTC_DR >> RTC_DR_DU_SHIFT) & RTC_DR_DU_MASK); // get day
	if (day > 0) { // day has been initialized, but starts with 1
		day--; // fix for calculation
	}
	const uint8_t hour = ((RTC_TR >> RTC_TR_HT_SHIFT) & RTC_TR_HT_MASK) * 10 + ((RTC_TR >> RTC_TR_HU_SHIFT) & RTC_TR_HU_MASK); // get hours
	const uint8_t minute = ((RTC_TR >> RTC_TR_MNT_SHIFT) & RTC_TR_MNT_MASK) * 10 + ((RTC_TR >> RTC_TR_MNU_SHIFT) & RTC_TR_MNU_MASK); // get minutes
	const uint8_t second = ((RTC_TR >> RTC_TR_ST_SHIFT) & RTC_TR_ST_MASK) * 10 + ((RTC_TR >> RTC_TR_SU_SHIFT) & RTC_TR_SU_MASK); // get seconds
	const uint32_t seconds = ((((((((year * 12) + month) * 31) + day) * 24) + hour) * 60) + minute) * 60 + second; // convert to number of seconds
	return seconds;
}

/** show uptime
 *  @param[in] argument no argument required
 */
static void command_uptime(void* argument)
{
	(void)argument; // we won't use the argument
	const uint32_t uptime = rtc_to_seconds() - boot_time; // get time from internal RTC
	printf("uptime: %u.%02u:%02u:%02u\n", uptime / (24 * 60 * 60), (uptime / (60 * 60)) % 24, (uptime / 60) % 60, uptime % 60);
}

/** show date and time
 *  @param[in] argument date and time to set
 */
static void command_datetime(void* argument)
{
	char* datetime = (char*)argument; // argument is optional date time
	const char* days[] = { "??", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}; // the days of the week

	// set date
	if (datetime) { // date has been provided
		// parse date
		const char* malformed = "date and time malformed, expecting YYYY-MM-DD WD HH:MM:SS\n";
		if (strlen(datetime) != (4 + 1 + 2 + 1 + 2) + 1 + 2 + 1 + (2 + 1 + 2 + 1 + 2)) { // verify date/time is long enough
			printf(malformed);
			return;
		}
		if (!(isdigit((int8_t)datetime[0]) && isdigit((int8_t)datetime[1]) && isdigit((int8_t)datetime[2]) && isdigit((int8_t)datetime[3]) && \
			'-' == datetime[4] && \
			isdigit((int8_t)datetime[5]) && isdigit((int8_t)datetime[6]) && \
			'-' == datetime[7] && \
			isdigit((int8_t)datetime[8]) && isdigit((int8_t)datetime[9]) && \
			' ' == datetime[10] && \
			isalpha((int8_t)datetime[11]) && isalpha((int8_t)datetime[12]) && \
			' ' == datetime[13] && \
			isdigit((int8_t)datetime[14]) && isdigit((int8_t)datetime[15]) && \
			':' == datetime[16] && \
			isdigit((int8_t)datetime[17]) && isdigit((int8_t)datetime[18]) && \
			':' == datetime[19] && \
			isdigit((int8_t)datetime[20]) && isdigit((int8_t)datetime[21]))) { // verify format (good enough to not fail parsing)
			printf(malformed);
			return;
		}
		const uint16_t year = strtol(&datetime[0], NULL, 10); // parse year
		if (year <= 2000 || year > 2099) {
			puts("year out of range\n");
			return;
		}
		const uint8_t month = strtol(&datetime[5], NULL, 10); // parse month
		if (month < 1 || month > 12) {
			puts("month out of range\n");
			return;
		}
		const uint8_t day = strtol(&datetime[8], NULL, 10); // parse day
		if (day < 1 || day > 31) {
			puts("day out of range\n");
			return;
		}
		const uint8_t hour = strtol(&datetime[14], NULL, 10); // parse hour
		if (hour > 24) {
			puts("hour out of range\n");
			return;
		}
		const uint8_t minute = strtol(&datetime[17], NULL, 10); // parse minutes
		if (minute > 59) {
			puts("minute out of range\n");
			return;
		}
		const uint8_t second = strtol(&datetime[30], NULL, 10); // parse seconds
		if (second > 59) {
			puts("second out of range\n");
			return;
		}
		uint8_t week_day = 0;
		for (uint8_t i = 1; i < LENGTH(days) && 0 == week_day; i++) {
			if (days[i][0] == toupper(datetime[11]) && days[i][1] == tolower(datetime[12])) {
				week_day = i;
				break;
			}
		}
		if (0 == week_day) {
			puts("unknown week day\n");
			return;
		}
		uint32_t date = 0; // to build the date
		date |= (((year - 2000) / 10) & RTC_DR_YT_MASK) << RTC_DR_YT_SHIFT; // set year tenth
		date |= (((year - 2000) % 10) & RTC_DR_YU_MASK) << RTC_DR_YU_SHIFT; // set year unit
		date |= ((month / 10) & RTC_DR_MT_MASK) << RTC_DR_MT_SHIFT; // set month tenth
		date |= ((month % 10) & RTC_DR_MU_MASK) << RTC_DR_MU_SHIFT; // set month unit
		date |= ((day / 10) & RTC_DR_DT_MASK) << RTC_DR_DT_SHIFT; // set day tenth
		date |= ((day % 10) & RTC_DR_DU_MASK) << RTC_DR_DU_SHIFT; // set day unit
		date |= (week_day & RTC_DR_WDU_MASK) << RTC_DR_WDU_SHIFT; // time day of the week
		uint32_t time = 0; // to build the time
		time = 0; // reset time
		time |= ((hour / 10) & RTC_TR_HT_MASK) << RTC_TR_HT_SHIFT; // set hour tenth
		time |= ((hour % 10) & RTC_TR_HU_MASK) << RTC_TR_HU_SHIFT; // set hour unit
		time |= ((minute / 10) & RTC_TR_MNT_MASK) << RTC_TR_MNT_SHIFT; // set minute tenth
		time |= ((minute % 10) & RTC_TR_MNU_MASK) << RTC_TR_MNU_SHIFT; // set minute unit
		time |= ((second / 10) & RTC_TR_ST_MASK) << RTC_TR_ST_SHIFT; // set second tenth
		time |= ((second % 10) & RTC_TR_SU_MASK) << RTC_TR_SU_SHIFT; // set second unit
		// write date
		pwr_disable_backup_domain_write_protect(); // disable backup protection so we can set the RTC clock source
		rtc_unlock(); // enable writing RTC registers
		RTC_ISR |= RTC_ISR_INIT; // enter initialisation mode
		while (!(RTC_ISR & RTC_ISR_INITF)); // wait to enter initialisation mode
		RTC_DR = date; // set date
		RTC_TR = time; // set time
		RTC_ISR &= ~RTC_ISR_INIT; // exit initialisation mode
		rtc_lock(); // protect RTC register against writing
		pwr_enable_backup_domain_write_protect(); // re-enable protection now that we configured the RTC clock
	}

	// show date
	if (!(RTC_ISR & RTC_ISR_INITS)) { // date has not been set yet
		puts("date/time not initialized\n");
	} else {
		rtc_wait_for_synchro(); // wait until date/time is synchronised
		const uint8_t year = ((RTC_DR >> RTC_DR_YT_SHIFT) & RTC_DR_YT_MASK) * 10 + ((RTC_DR >> RTC_DR_YU_SHIFT) & RTC_DR_YU_MASK); // get year
		const uint8_t month = ((RTC_DR >> RTC_DR_MT_SHIFT) & RTC_DR_MT_MASK) * 10 + ((RTC_DR >> RTC_DR_MU_SHIFT) & RTC_DR_MU_MASK); // get month
		const uint8_t day = ((RTC_DR >> RTC_DR_DT_SHIFT) & RTC_DR_DT_MASK) * 10 + ((RTC_DR >> RTC_DR_DU_SHIFT) & RTC_DR_DU_MASK); // get day
		const uint8_t week_day = ((RTC_DR >> RTC_DR_WDU_SHIFT) & RTC_DR_WDU_MASK); // get week day
		const uint8_t hour = ((RTC_TR >> RTC_TR_HT_SHIFT) & RTC_TR_HT_MASK) * 10 + ((RTC_TR >> RTC_TR_HU_SHIFT) & RTC_TR_HU_MASK); // get hours
		const uint8_t minute = ((RTC_TR >> RTC_TR_MNT_SHIFT) & RTC_TR_MNT_MASK) * 10 + ((RTC_TR >> RTC_TR_MNU_SHIFT) & RTC_TR_MNU_MASK); // get minutes
		const uint8_t second = ((RTC_TR >> RTC_TR_ST_SHIFT) & RTC_TR_ST_MASK) * 10 + ((RTC_TR >> RTC_TR_SU_SHIFT) & RTC_TR_SU_MASK); // get seconds
		printf("date: 20%02d-%02d-%02d %s %02d:%02d:%02d\n", year, month, day, days[week_day], hour, minute, second);
	}
}

/** reset board
 *  @param[in] argument no argument required
 */
static void command_reset(void* argument)
{
	(void)argument; // we won't use the argument
	scb_reset_system(); // reset device
	while (true); // wait for the reset to happen
}

/** switch to system memory (e.g. embedded bootloader)
 *  @param[in] argument no argument required
 */
static void command_system(void* argument)
{
	(void)argument; // we won't use the argument
	system_memory(); // jump to system memory
}

/** switch to DFU bootloader
 *  @param[in] argument no argument required
 */
static void command_bootloader(void* argument)
{
	(void)argument; // we won't use the argument
	dfu_bootloader(); // start DFU bootloader
}

/** list of all supported commands */
static const struct menu_command_t menu_commands[] = {
	{
		.shortcut = 'h',
		.name = "help",
		.command_description = "display help",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &command_help,
	},
	{
		.shortcut = 'v',
		.name = "version",
		.command_description = "show software and hardware version",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &command_version,
	},
	{
		.shortcut = 'u',
		.name = "uptime",
		.command_description = "show uptime",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &command_uptime,
	},
	{
		.shortcut = 'd',
		.name = "date",
		.command_description = "show/set date and time",
		.argument = MENU_ARGUMENT_STRING,
		.argument_description = "[YYYY-MM-DD HH:MM:SS]",
		.command_handler = &command_datetime,
	},
	{
		.shortcut = 'r',
		.name = "reset",
		.command_description = "reset board",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &command_reset,
	},
	{
		.shortcut = 's',
		.name = "system",
		.command_description = "reboot into system memory",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &command_system,
	},
	{
		.shortcut = 'b',
		.name = "bootloader",
		.command_description = "reboot into DFU bootloader",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &command_bootloader,
	},
};

static void command_help(void* argument)
{
	(void)argument; // we won't use the argument
	printf("available commands:\n");
	menu_print_commands(menu_commands, LENGTH(menu_commands)); // print global commands
}

/** process user command
 *  @param[in] str user command string (\0 ended)
 */
static void process_command(char* str)
{
	// ensure actions are available
	if (NULL == menu_commands || 0 == LENGTH(menu_commands)) {
		return;
	}
	// don't handle empty lines
	if (!str || 0 == strlen(str)) {
		return;
	}
	bool command_handled = false;
	if (!command_handled) {
		command_handled = menu_handle_command(str, menu_commands, LENGTH(menu_commands)); // try if this is not a global command
	}
	if (!command_handled) {
		printf("command not recognized. enter help to list commands\n");
	}
}

/** program entry point
 *  this is the firmware function started by the micro-controller
 */
void main(void);
void main(void)
{

#if DEBUG
	// enable functionalities for easier debug
	DBGMCU_CR |= DBGMCU_CR_IWDG_STOP; // stop independent watchdog counter when code is halted
	DBGMCU_CR |= DBGMCU_CR_WWDG_STOP; // stop window watchdog counter when code is halted
	DBGMCU_CR |= DBGMCU_CR_STANDBY; // allow debug also in standby mode (keep digital part and clock powered)
	DBGMCU_CR |= DBGMCU_CR_STOP; // allow debug also in stop mode (keep clock powered)
	DBGMCU_CR |= DBGMCU_CR_SLEEP; // allow debug also in sleep mode (keep clock powered)
#else
	// setup watchdog to reset in case we get stuck (i.e. when an error occurred)
	iwdg_set_period_ms(WATCHDOG_PERIOD); // set independent watchdog period
	iwdg_start(); // start independent watchdog
#endif

	board_setup(); // setup board
	uart_setup(); // setup USART (for printing)
	usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
	puts("\nwelcome to the CuVoodoo STM32F4 example firmware\n"); // print welcome message

#if DEBUG
	// show reset cause
	if (RCC_CSR & (RCC_CSR_LPWRRSTF | RCC_CSR_WWDGRSTF | RCC_CSR_IWDGRSTF | RCC_CSR_SFTRSTF | RCC_CSR_PORRSTF | RCC_CSR_PINRSTF)) {
		puts("reset cause(s):");
		if (RCC_CSR & RCC_CSR_LPWRRSTF) {
			puts(" low-power");
		}
		if (RCC_CSR & RCC_CSR_WWDGRSTF) {
			puts(" window-watchdog");
		}
		if (RCC_CSR & RCC_CSR_IWDGRSTF) {
			puts(" independent-watchdog");
		}
		if (RCC_CSR & RCC_CSR_SFTRSTF) {
			puts(" software");
		}
		if (RCC_CSR & RCC_CSR_PORRSTF) {
			puts(" POR/PDR");
		}
		if (RCC_CSR & RCC_CSR_PINRSTF) {
			puts(" pin");
		}
		putc('\n');
		RCC_CSR |= RCC_CSR_RMVF; // clear reset flags
	}
#endif

	// setup RTC
	puts_debug("setup RTC: ");
	rcc_periph_clock_enable(RCC_RTC); // enable clock for RTC peripheral
	if (!(RCC_BDCR && RCC_BDCR_RTCEN)) { // the RTC has not been configured yet
		pwr_disable_backup_domain_write_protect(); // disable backup protection so we can set the RTC clock source
		rtc_unlock(); // enable writing RTC registers
#if defined(MINIF401)
		rcc_osc_on(RCC_LSE); // enable LSE clock
		while (!rcc_is_osc_ready(RCC_LSE)); // wait until clock is ready
		rtc_set_prescaler(256, 128); // set clock prescaler to 32768
		RCC_BDCR =  (RCC_BDCR & ~(RCC_BDCR_RTCSEL_MASK << RCC_BDCR_RTCSEL_SHIFT)) | (RCC_BDCR_RTCSEL_LSE << RCC_BDCR_RTCSEL_SHIFT); // select LSE as RTC clock source
#else
		rcc_osc_on(RCC_LSI); // enable LSI clock
		while (!rcc_is_osc_ready(RCC_LSI)); // wait until clock is ready
		rtc_set_prescaler(250, 128); // set clock prescaler to 32000
		RCC_BDCR =  (RCC_BDCR & ~(RCC_BDCR_RTCSEL_MASK << RCC_BDCR_RTCSEL_SHIFT)) | (RCC_BDCR_RTCSEL_LSI << RCC_BDCR_RTCSEL_SHIFT); // select LSI as RTC clock source
#endif
		RCC_BDCR |= RCC_BDCR_RTCEN; // enable RTC
		rtc_lock(); // protect RTC register against writing
		pwr_enable_backup_domain_write_protect(); // re-enable protection now that we configured the RTC clock
	}
	boot_time = rtc_to_seconds(); // remember the start time
	puts_debug("OK\n");

	// setup wakeup timer for periodic checks
	puts_debug("setup wakeup: ");
	// RTC needs to be configured beforehand
	pwr_disable_backup_domain_write_protect(); // disable backup protection so we can write to the RTC registers
	rtc_unlock(); // enable writing RTC registers
	rtc_clear_wakeup_flag(); // clear flag for fresh start
#if defined(MINIF401)
	rtc_set_wakeup_time((32768 / 2) / WAKEUP_FREQ - 1, RTC_CR_WUCLKSEL_RTC_DIV2); // set wakeup time based on LSE (keep highest precision, also enables the wakeup timer)
#else
	rtc_set_wakeup_time((32000 / 2) / WAKEUP_FREQ - 1, RTC_CR_WUCLKSEL_RTC_DIV2); // set wakeup time based on LSI (keep highest precision, also enables the wakeup timer)
#endif
	rtc_enable_wakeup_timer_interrupt(); // enable interrupt
	rtc_lock(); // disable writing RTC registers
	// important: do not re-enable backup_domain_write_protect, since this will prevent clearing flags (but RTC registers do not need to be unlocked)
	puts_debug("OK\n");

	// setup terminal
	terminal_prefix = ""; // set default prefix
	terminal_process = &process_command; // set central function to process commands
	terminal_setup(); // start terminal

	// start main loop
	bool action = false; // if an action has been performed don't go to sleep
	button_flag = false; // reset button flag
	led_on(); // switch LED to indicate booting completed
	while (true) { // infinite loop
		iwdg_reset(); // kick the dog
		if (user_input_available) { // user input is available
			action = true; // action has been performed
			led_toggle(); // toggle LED
			char c = user_input_get(); // store receive character
			terminal_send(c); // send received character to terminal
		}
		if (button_flag) { // user pressed button
			action = true; // action has been performed
			puts("button pressed\n");
			led_toggle(); // toggle LED
			sleep_ms(100); // wait a bit to remove noise and double trigger
			button_flag = false; // reset flag
		}
		if (wakeup_flag) { // time to do periodic checks
			wakeup_flag = false; // clear flag
		}
		if (second_flag) { // one second passed
			second_flag = false; // clear flag
			led_toggle(); // toggle LED to indicate if main function is stuck
		}
		if (action) { // go to sleep if nothing had to be done, else recheck for activity
			action = false;
		} else {
			__WFI(); // go to sleep
		}
	} // main loop
}

/** interrupt service routine when the wakeup timer triggered */
void rtc_wkup_isr(void)
{
	static uint16_t tick = WAKEUP_FREQ; // how many wakeup have occurred
	exti_reset_request(EXTI22); // clear EXTI flag used by wakeup
	rtc_clear_wakeup_flag(); // clear flag
	wakeup_flag = true; // notify main loop
	tick--; // count the number of ticks down (do it in the ISR to no miss any tick)
	if (0 == tick) { // count down completed
		second_flag = true; // notify main loop a second has passed
		tick = WAKEUP_FREQ; // restart count down
	}
}