aboutsummaryrefslogtreecommitdiff
path: root/application.c
blob: 637dae2728f1ce86c7809d9c3b029208c2db654b (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
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
/** STM32F1 application to strobe electricity
 *  @file
 *  @author King Kévin <kingkevin@cuvoodoo.info>
 *  @date 2016-2018
 */

/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // standard utilities
#include <string.h> // string utilities

/* 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
#include "ir_nec.h" // InfraRed NEC decoding utilities

#define WATCHDOG_PERIOD 20000 /**< watchdog period in ms */

/** @defgroup main_flags flag set in interrupts to be processed in main task
 *  @{
 */
volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
/** @} */

/** if the strobe output should not flicker */
static bool flicker_off = true;

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   
}

/** 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);

/** show uptime
 *  @param[in] argument no argument required
 */
static void command_uptime(void* argument);

/** reset board
 *  @param[in] argument no argument required
 */
static void command_reset(void* argument);

/** switch to DFU bootloader
 *  @param[in] argument no argument required
 */
static void command_bootloader(void* argument);

/** 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 = 'r',
		.name = "reset",
		.command_description = "reset board",
		.argument = MENU_ARGUMENT_NONE,
		.argument_description = NULL,
		.command_handler = &command_reset,
	},
	{
		.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
}

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
	// get device identifier (DEV_ID)
	// 0x412: low-density, 16-32 kB flash
	// 0x410: medium-density, 64-128 kB flash
	// 0x414: high-density, 256-512 kB flash
	// 0x430: XL-density, 768-1024 kB flash
	// 0x418: connectivity
	printf("device family: ");
	switch (DBGMCU_IDCODE&DBGMCU_IDCODE_DEV_ID_MASK) {
		case 0: // this is a known issue document in STM32F10xxC/D/E Errata sheet, without workaround
			printf("unreadable\n");
			break;
		case 0x412:
			printf("low-density\n");
			break;
		case 0x410:
			printf("medium-density\n");
			break;
		case 0x414:
			printf("high-density\n");
			break;
		case 0x430:
			printf("XL-density\n");
			break;
		case 0x418:
			printf("connectivity\n");
			break;
		default:
			printf("unknown\n");
			break;
	}
	// show flash size
	printf("flash size: ");
	if (0xffff==DESIG_FLASH_SIZE) {
		printf("unknown (probably a defective micro-controller\n");
	} else {
		printf("%u KB\n", DESIG_FLASH_SIZE);
	}
	// display device identity
	printf("device id: %08x%08x%08x\n", DESIG_UNIQUE_ID0, DESIG_UNIQUE_ID1, DESIG_UNIQUE_ID2);
}

static void command_uptime(void* argument)
{
	(void)argument; // we won't use the argument
	uint32_t uptime = rtc_get_counter_val(); // 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);
}

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
}

static void command_bootloader(void* argument)
{
	(void)argument; // we won't use the argument
	RCC_CSR |= RCC_CSR_RMVF; // clear reset flags
	scb_reset_core(); // reset core (the bootloader will interpret it as starting into DFU)
	while (true); // wait for the reset to happen
}

/** 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");
	}
}

#define STROBE_PORT B /**< GPIO port to control strobe light */
#define STROBE_PIN 6 /**< GPIO pin to control strobe light */
#define STROBE_ON 0 /**< LED is on when pin is low (open-drain allows 5V on) */

/* strobe animations (on + off times in ms) */
static const uint16_t strobe1[] = {100, 0};
static const uint16_t strobe2[] = {100, 100, 100, 0};
static const uint16_t strobe3[] = {100, 100, 100, 100, 100, 0};
static const uint16_t strobe5[] = {50, 50, 50, 50, 50, 50, 50, 50, 50, 0};
static const uint16_t strobe10[] = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100};

/** switch strobe power on */
static void strobe_on(void)
{
#if STROBE_ON
	gpio_set(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
#else
	gpio_clear(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
#endif
}

/** switch strobe power off */
static void strobe_off(void)
{
#if STROBE_ON
	gpio_clear(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
#else
	gpio_set(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
#endif
}

/** toggle strobe power */
static void strobe_toggle(void)
{
	gpio_toggle(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
}

/** play strobe animation
 *  @param[in] animation on+off timings (in ms)
 *  @param[in] length animation length
 */
static void strobe_play(const uint16_t* animation, uint16_t length)
{
	for (uint16_t i = 0; i < length; i++) {
		iwdg_reset(); // kick the dog
		if (0 == animation[i]) { // skip animation
			continue;
		}
		if (i % 2) { // odd index if encodes off duration
			strobe_off();
		} else {
			strobe_on();
		}
		sleep_ms(animation[i]); // wait for set duration
	}
	strobe_off(); // switch off at the end
}

/** perform IR code related action 
 *  @warning the codes need to be adjusted to your remote
 *  @param[in] code IR code
 */
static void ir_action(const struct ir_nec_code_t* code)
{
	if (code->repeat) { // don't handle long button press repeating the code
		return;
	}
	if (0x00f7 == code->address) { // flat LED IR remote
		switch (code->command) { // choose animation depending on button
		case 0x00: // UP
			strobe_toggle();
			printf("toggle strobe\n");
			break;
		case 0x80: // DOWN
			printf("start flickering\n");
			flicker_off = false; // let flickering happen in main loop
			break;
		case 0xc0: // ON
			strobe_on();
			printf("light on\n");
			break;
		case 0x40: // OFF
			strobe_off();
			flicker_off = true; // stop flickering
			printf("light off\n");
			break;
		case 0x20: // red
		case 0x10: // orange
		case 0x30: // orange
		case 0x08: // orange
		case 0x28: // organe
			printf("1 strobe\n");
			strobe_play(strobe1, LENGTH(strobe1));
			break;
		case 0xa0: // green
		case 0x90: // green
		case 0xb0: // green
		case 0x88: // green
		case 0xa8: // green
			printf("2 strobes\n");
			strobe_play(strobe2, LENGTH(strobe2));
			break;
		case 0x60: // blue
		case 0x50: // blue
		case 0x70: // blue
		case 0x48: // blue
		case 0x68: // blue
			printf("3 strobes\n");
			strobe_play(strobe3, LENGTH(strobe3));
			break;
		case 0xe0: // W
		case 0xd0: // flash
		case 0xc8: // fade
		case 0xf0: // strobe
			printf("5 strobes\n");
			strobe_play(strobe5, LENGTH(strobe5));
			break;
		case 0xe8: // smooth
			printf("10 strobes\n");
			strobe_play(strobe10, LENGTH(strobe10));
			break;
		default:
			printf("unknown code\n");
			break;
		}
	} else if (0x407f == code->address) { // iDual remote
		switch (code->command) { // choose animation depending on button
		case 0x08: // brightness down
			strobe_toggle();
			printf("toggle strobe\n");
			break;
		case 0x90: // brightness up
			printf("start flickering\n");
			flicker_off = false; // let flickering happen in main loop
			break;
		case 0x80: // ON
			strobe_on();
			printf("light on\n");
			break;
		case 0x40: // OFF
			strobe_off();
			flicker_off = true; // stop flickering
			printf("light off\n");
			break;
		case 0x88: // left
		case 0x48:
		case 0xc8:
		case 0x28:
			printf("1 strobe\n");
			strobe_play(strobe1, LENGTH(strobe1));
			break;
		case 0x68: // middle-left
		case 0xe8:
		case 0x18:
		case 0x98:
			printf("2 strobes\n");
			strobe_play(strobe2, LENGTH(strobe2));
			break;
		case 0x50: // middle-right
		case 0xd0:
		case 0x30:
		case 0xb0:
			printf("3 strobes\n");
			strobe_play(strobe3, LENGTH(strobe3));
			break;
		case 0xc0: // right
		case 0x20:
		case 0xa0:
		case 0x60:
			printf("5 strobes\n");
			strobe_play(strobe5, LENGTH(strobe5));
			break;
		case 0x70: // circle
		case 0xa8:
		case 0x58:
		case 0xf0:
		case 0xe0:
		case 0x10:
			printf("10 strobes\n");
			strobe_play(strobe10, LENGTH(strobe10));
			break;
		default:
			printf("unknown code\n");
			break;
		}
	} else {
		printf("unknown remote\n");
	}
}

/** program entry point
 *  this is the firmware function started by the micro-controller
 */
void main(void);
void main(void)
{
	rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock

	board_setup(); // setup board
	uart_setup(); // setup USART (for printing)
	usb_cdcacm_setup(); // setup USB CDC ACM (for printing and DFU)
	// setup strobe pin
	rcc_periph_clock_enable(RCC_GPIO(STROBE_PORT)); // enable clock for GPIO port peripheral
#if STROBE_ON
	gpio_set_mode(GPIO(STROBE_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(STROBE_PIN)); // set pin to output push-pull do drive strobe signal
#else
	gpio_set_mode(GPIO(STROBE_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(STROBE_PIN)); // set pin to output open-drain do enable strobe
#endif
	strobe_off(); // switch off strobe per defaulf
	ir_nec_setup(true); // setup ID NEC code decoder
	printf("\nwelcome to the CuVoodoo STM32F1 spark strober\n"); // print welcome message

	// setup RTC
	printf("setup internal RTC: ");
	rtc_auto_awake(RCC_LSE, 32768-1); // ensure internal RTC is on, uses the 32.678 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect)
	rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
	nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
	printf("OK\n");

#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

#if !(DEBUG)
	// show watchdog information
	printf("setup watchdog: %.2fs",WATCHDOG_PERIOD/1000.0);
	if (FLASH_OBR&FLASH_OBR_OPTERR) {
		printf(" (option bytes not set in flash: software wachtdog used, not automatically started at reset)\n");
	} else if (FLASH_OBR&FLASH_OBR_WDG_SW) {
		printf(" (software wachtdog used, not automatically started at reset)\n");
	} else {
		printf(" (hardware wachtdog used, automatically started at reset)\n");
	}
#endif


	// 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
	bool flicker_on = false; // if the flicker strobe is currently on
	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
			printf("button pressed\n");
			led_toggle(); // toggle LED
			for (uint32_t i=0; i<1000000; i++) { // wait a bit to remove noise and double trigger
				__asm__("nop");
			}
			button_flag = false; // reset flag
		}
		if (rtc_internal_tick_flag) { // the internal RTC ticked
			rtc_internal_tick_flag = false; // reset flag
			action = true; // action has been performed
#if !defined(BLUE_PILL) // on the blue pill the LED is close to the 32.768 kHz oscillator and heavily influences it
			//led_toggle(); // toggle LED (good to indicate if main function is stuck)
#endif
		}
		if (ir_nec_code_received_flag) { // IR code received
			ir_nec_code_received_flag = false; // reset flag
			led_on(); // notify user we received a code
			printf("IR NEC code received: addr=%+04x, cmd=%+02x%s\n", ir_nec_code_received.address, ir_nec_code_received.command, ir_nec_code_received.repeat ? " (repeat)" : "");
			if (!ir_nec_code_received.repeat) { // ignore repeated codes
				ir_action(&ir_nec_code_received); // handle IR code
			}
			led_off(); // notify user we received a code
		}
		if (!flicker_off) {
			action = true; // prevent going to sleep
			uint32_t time = rand();
			if (flicker_on) {
				time %= 1000;
				if (time < 100) {
					time = 100;
				}
				strobe_off();
			} else {
				time %= 100;
				if (time < 10) {
					time = 10;
				}
				strobe_on();
			}
			sleep_ms(time);
			flicker_on = !flicker_on;
		}
		if (action) { // go to sleep if nothing had to be done, else recheck for activity
			action = false;
		} else {
			__WFI(); // go to sleep
		}
	} // main loop
}

/** @brief interrupt service routine called when tick passed on RTC */
void rtc_isr(void)
{
	rtc_clear_flag(RTC_SEC); // clear flag
	rtc_internal_tick_flag = true; // notify to show new time
}