parent
							
								
									11764cbd7a
								
							
						
					
					
						commit
						d01e40e174
					
				| @ -0,0 +1,157 @@ | ||||
| #daemon configuration | ||||
| telnet_port 4444 | ||||
| gdb_port 3333 | ||||
| 
 | ||||
| #interface | ||||
| #cat /usr/lib/openocd/interface/olimex-arm-usb-ocd.cfg >> openocd.cfg | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| # | ||||
| # STMicroelectronics ST-LINK/V2 in-circuit debugger/programmer | ||||
| # | ||||
| 
 | ||||
| interface hla | ||||
| hla_layout stlink | ||||
| hla_device_desc "ST-LINK/V2" | ||||
| hla_vid_pid 0x0483 0x3748 | ||||
| 
 | ||||
| # Optionally specify the serial number of ST-LINK/V2 usb device.  ST-LINK/V2 | ||||
| # devices seem to have serial numbers with unreadable characters.  ST-LINK/V2 | ||||
| # firmware version >= V2.J21.S4 recommended to avoid issues with adapter serial | ||||
| # number reset issues. | ||||
| # eg. | ||||
| #hla_serial "\xaa\xbc\x6e\x06\x50\x75\xff\x55\x17\x42\x19\x3f" | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| # target configuration | ||||
| # cat /usr/lib/openocd/target/stm32.cfg >> openocd.cfg | ||||
| 
 | ||||
| # script for stm32f3x family | ||||
| 
 | ||||
| # | ||||
| # stm32 devices support both JTAG and SWD transports. | ||||
| # | ||||
| source [find target/swj-dp.tcl] | ||||
| source [find mem_helper.tcl] | ||||
| 
 | ||||
| if { [info exists CHIPNAME] } { | ||||
|    set _CHIPNAME $CHIPNAME | ||||
| } else { | ||||
|    set _CHIPNAME stm32f3x | ||||
| } | ||||
| 
 | ||||
| set _ENDIAN little | ||||
| 
 | ||||
| # Work-area is a space in RAM used for flash programming | ||||
| # By default use 16kB | ||||
| if { [info exists WORKAREASIZE] } { | ||||
|    set _WORKAREASIZE $WORKAREASIZE | ||||
| } else { | ||||
|    set _WORKAREASIZE 0x4000 | ||||
| } | ||||
| 
 | ||||
| # JTAG speed should be <= F_CPU/6. F_CPU after reset is 8MHz, so use F_JTAG = 1MHz | ||||
| # | ||||
| # Since we may be running of an RC oscilator, we crank down the speed a | ||||
| # bit more to be on the safe side. Perhaps superstition, but if are | ||||
| # running off a crystal, we can run closer to the limit. Note | ||||
| # that there can be a pretty wide band where things are more or less stable. | ||||
| adapter_khz 1000 | ||||
| 
 | ||||
| adapter_nsrst_delay 100 | ||||
| if {[using_jtag]} { | ||||
|  jtag_ntrst_delay 100 | ||||
| } | ||||
| 
 | ||||
| #jtag scan chain | ||||
| if { [info exists CPUTAPID] } { | ||||
|    set _CPUTAPID $CPUTAPID | ||||
| } else { | ||||
|    if { [using_jtag] } { | ||||
|       # See STM Document RM0316 | ||||
|       # Section 29.6.3 - corresponds to Cortex-M4 r0p1 | ||||
|       set _CPUTAPID 0x4ba00477 | ||||
|    } { | ||||
|       set _CPUTAPID 0x2ba01477 | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID | ||||
| 
 | ||||
| if { [info exists BSTAPID] } { | ||||
|    set _BSTAPID $BSTAPID | ||||
| } else { | ||||
|   # STM Document RM0316 rev 5 for STM32F302/303 B/C size | ||||
|   set _BSTAPID1 0x06422041 | ||||
|   # STM Document RM0313 rev 3 for STM32F37x | ||||
|   set _BSTAPID2 0x06432041 | ||||
|   #  STM Document RM364 rev 1 for STM32F334 | ||||
|   set _BSTAPID3 0x06438041 | ||||
|   #  STM Document RM316 rev 5 for STM32F303 6/8 size | ||||
|   #  STM Document RM365 rev 3 for STM32F302 6/8 size | ||||
|   #  STM Document RM366 rev 2 for STM32F301 6/8 size | ||||
|   set _BSTAPID4 0x06439041 | ||||
|   #  STM Document RM016 rev 5 for STM32F303 D/E size | ||||
|   set _BSTAPID5 0x06446041 | ||||
| } | ||||
| 
 | ||||
| if {[using_jtag]} { | ||||
|  swj_newdap $_CHIPNAME bs -irlen 5 -expected-id $_BSTAPID1 \ | ||||
|  -expected-id $_BSTAPID2 -expected-id $_BSTAPID3 \ | ||||
|  -expected-id $_BSTAPID4 -expected-id $_BSTAPID5 | ||||
| } | ||||
| 
 | ||||
| set _TARGETNAME $_CHIPNAME.cpu | ||||
| target create $_TARGETNAME cortex_m -endian $_ENDIAN -chain-position $_TARGETNAME | ||||
| 
 | ||||
| $_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 | ||||
| 
 | ||||
| set _FLASHNAME $_CHIPNAME.flash | ||||
| flash bank $_FLASHNAME stm32f1x 0 0 0 0 $_TARGETNAME | ||||
| 
 | ||||
| reset_config srst_nogate | ||||
| 
 | ||||
| if {![using_hla]} { | ||||
|    # if srst is not fitted use SYSRESETREQ to | ||||
|    # perform a soft reset | ||||
|    cortex_m reset_config sysresetreq | ||||
| } | ||||
| 
 | ||||
| proc stm32f3x_default_reset_start {} { | ||||
| 	# Reset clock is HSI (8 MHz) | ||||
| 	adapter_khz 1000 | ||||
| } | ||||
| 
 | ||||
| proc stm32f3x_default_examine_end {} { | ||||
| 	# Enable debug during low power modes (uses more power) | ||||
| 	mmw 0xe0042004 0x00000007 0 ;# DBGMCU_CR |= DBG_STANDBY | DBG_STOP | DBG_SLEEP | ||||
| 
 | ||||
| 	# Stop watchdog counters during halt | ||||
| 	mww 0xe0042008 0x00001800 ;# DBGMCU_APB1_FZ = DBG_IWDG_STOP | DBG_WWDG_STOP | ||||
| } | ||||
| 
 | ||||
| proc stm32f3x_default_reset_init {} { | ||||
| 	# Configure PLL to boost clock to HSI x 8 (64 MHz) | ||||
| 	mww 0x40021004 0x00380400   ;# RCC_CFGR = PLLMUL[3:1] | PPRE1[2] | ||||
| 	mmw 0x40021000 0x01000000 0 ;# RCC_CR |= PLLON | ||||
| 	mww 0x40022000 0x00000012   ;# FLASH_ACR = PRFTBE | LATENCY[1] | ||||
| 	sleep 10                    ;# Wait for PLL to lock | ||||
| 	mmw 0x40021004 0x00000002 0 ;# RCC_CFGR |= SW[1] | ||||
| 
 | ||||
| 	# Boost JTAG frequency | ||||
| 	adapter_khz 8000 | ||||
| } | ||||
| 
 | ||||
| # Default hooks | ||||
| $_TARGETNAME configure -event examine-end { stm32f3x_default_examine_end } | ||||
| $_TARGETNAME configure -event reset-start { stm32f3x_default_reset_start } | ||||
| $_TARGETNAME configure -event reset-init { stm32f3x_default_reset_init } | ||||
| 
 | ||||
| $_TARGETNAME configure -event trace-config { | ||||
| 	# Set TRACE_IOEN; TRACE_MODE is set to async; when using sync | ||||
| 	# change this value accordingly to configure trace pins | ||||
| 	# assignment | ||||
| 	mmw 0xe0042004 0x00000020 0 | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| #!/usr/bin/env php | ||||
| <?php | ||||
| 
 | ||||
| $map = file_get_contents('main.map'); | ||||
| 
 | ||||
| $at_flash = strpos($map, '.text           0x0000000008000000'); | ||||
| $at_ram   = strpos($map, '.data           0x0000000020000000'); | ||||
| $at_dbg   = strpos($map, '.memory_b1_text'); | ||||
| 
 | ||||
| $discard = substr($map, 0, $at_flash); | ||||
| $flash = substr($map, $at_flash, $at_ram - $at_flash); | ||||
| $ram = substr($map, $at_ram, $at_dbg - $at_ram); | ||||
| 
 | ||||
| $flash = str_replace("lib/gcc/arm-none-eabi/5.3.0/../../../../", "", $flash); | ||||
| 
 | ||||
| file_put_contents("main.flash.map", $flash); | ||||
| file_put_contents("main.ram.map", $ram); | ||||
| 
 | ||||
| //preg_match("/\*\(\.eh_frame\)\n\s*0x00000000(2[0-9a-f]+)\s*\. = ALIGN \(0x4\)/i", $ram, $m); | ||||
| 
 | ||||
| //echo "Free RAM: " . (0x20010000 - hexdec($m[1])) . " B\n"; | ||||
| @ -0,0 +1,159 @@ | ||||
| #include "event_handler.h" | ||||
| #include "com/debug.h" | ||||
| 
 | ||||
| typedef struct { | ||||
| 	uint32_t handler_id; | ||||
| 	uint32_t chained_handler; // if not 0, that handler is removed together with this handler.
 | ||||
| 	EventType type; // event type
 | ||||
| 	EventHandlerCallback handler; // returns True if event was handled.
 | ||||
| 	bool used; // this slot is currently used
 | ||||
| 
 | ||||
| 	void *user_data; | ||||
| 
 | ||||
| } EventHandlerSlot; | ||||
| 
 | ||||
| 
 | ||||
| #define EH_SLOT_COUNT 6 | ||||
| static EventHandlerSlot eh_slots[EH_SLOT_COUNT]; | ||||
| 
 | ||||
| static uint32_t next_slot_pid = 1; // 0 is reserved
 | ||||
| 
 | ||||
| /** Get a valid free PID for a new handler slot. */ | ||||
| static uint32_t make_pid(void) | ||||
| { | ||||
| 	uint32_t pid = next_slot_pid++; | ||||
| 
 | ||||
| 	// make sure no task is given PID 0
 | ||||
| 	if (next_slot_pid == 0) { | ||||
| 		next_slot_pid++; | ||||
| 	} | ||||
| 
 | ||||
| 	return pid; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Register an event handler for event type | ||||
|  * @param type    : handled event type | ||||
|  * @param handler : the handler func | ||||
|  * @return handler ID. Can be used to remove the handler. | ||||
|  */ | ||||
| uint32_t register_event_handler(EventType type, EventHandlerCallback handler, void *user_data) | ||||
| { | ||||
| 	for (int i = 0; i < EH_SLOT_COUNT; i++) { | ||||
| 		if (eh_slots[i].used) continue; | ||||
| 
 | ||||
| 		// Free slot found
 | ||||
| 		EventHandlerSlot *slot = &eh_slots[i]; | ||||
| 
 | ||||
| 		slot->handler = handler; | ||||
| 		slot->type = type; | ||||
| 		slot->handler_id = make_pid(); | ||||
| 		slot->used = true; | ||||
| 		slot->chained_handler = 0; | ||||
| 		slot->user_data = user_data; | ||||
| 
 | ||||
| 		return slot->handler_id; | ||||
| 	} | ||||
| 
 | ||||
| 	error("Failed to register event handler for type %d", type); | ||||
| 
 | ||||
| 	return 0; // fail
 | ||||
| } | ||||
| 
 | ||||
| /** Chain for common destruction */ | ||||
| bool chain_event_handler(uint32_t from, uint32_t to, bool reci) | ||||
| { | ||||
| 	uint8_t cnt = 0; | ||||
| 
 | ||||
| 	for (int i = 0; i < EH_SLOT_COUNT; i++) { | ||||
| 		EventHandlerSlot *slot = &eh_slots[i]; | ||||
| 
 | ||||
| 		if (!slot->used) continue; | ||||
| 
 | ||||
| 		if (slot->handler_id == from) { | ||||
| 			slot->chained_handler = to; | ||||
| 			cnt++; | ||||
| 		} | ||||
| 
 | ||||
| 		// link back in two-handler reciprocal link
 | ||||
| 		if (reci && slot->handler_id == to) { | ||||
| 			slot->chained_handler = from; | ||||
| 			cnt++; | ||||
| 		} | ||||
| 
 | ||||
| 		if (cnt == (reci ? 2 : 1)) { | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief check if exists | ||||
|  */ | ||||
| bool event_handler_exists(uint32_t handler_id) | ||||
| { | ||||
| 	for (int i = 0; i < EH_SLOT_COUNT; i++) { | ||||
| 		EventHandlerSlot *slot = &eh_slots[i]; | ||||
| 		if (!slot->used) continue; | ||||
| 		if (slot->handler_id == handler_id) { | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Remove event handler by handler ID | ||||
|  * @param handler_id : handler ID, obtained when registering or in the callback. | ||||
|  * @return number of removed handlers | ||||
|  */ | ||||
| int remove_event_handler(uint32_t handler_id) | ||||
| { | ||||
| 	int cnt = 0; | ||||
| 	while (handler_id != 0) { // outer loop because of chained handlers
 | ||||
| 		bool suc = false; | ||||
| 		for (int i = 0; i < EH_SLOT_COUNT; i++) { | ||||
| 			if (!eh_slots[i].used) { | ||||
| 				continue; // skip empty slot
 | ||||
| 			} | ||||
| 
 | ||||
| 			// Free slot found
 | ||||
| 			EventHandlerSlot *slot = &eh_slots[i]; | ||||
| 			if (slot->handler_id == handler_id) { | ||||
| 				slot->used = false; | ||||
| 				slot->user_data = NULL; | ||||
| 				suc = true; | ||||
| 				cnt++; | ||||
| 
 | ||||
| 				handler_id = slot->chained_handler; // continue with the chained handler.
 | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		if (!suc) break; | ||||
| 	} | ||||
| 	return cnt; | ||||
| } | ||||
| 
 | ||||
| /** Handle an event */ | ||||
| void run_event_handler(Event *evt) | ||||
| { | ||||
| 	bool handled = false; | ||||
| 
 | ||||
| 	for (int i = 0; i < EH_SLOT_COUNT; i++) { | ||||
| 		EventHandlerSlot *slot = &eh_slots[i]; | ||||
| 
 | ||||
| 		if (!slot->used) continue; // unused
 | ||||
| 
 | ||||
| 		if (slot->type != evt->type) continue; // wrong type
 | ||||
| 
 | ||||
| 		handled = slot->handler(slot->handler_id, evt, &slot->user_data); | ||||
| 		if (handled) break; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!handled) { | ||||
| 		warn("Unhandled event, type %d", evt->type); | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,48 @@ | ||||
| #pragma once | ||||
| #include "main.h" | ||||
| #include "event_queue.h" | ||||
| 
 | ||||
| typedef bool (*EventHandlerCallback) (uint32_t hdlr_id, Event *evt, void **user_data); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Register an event handler for event type | ||||
|  * @param type    : handled event type | ||||
|  * @param handler : the handler func | ||||
|  * @return handler ID. Can be used to remove the handler. | ||||
|  */ | ||||
| uint32_t register_event_handler(EventType type, EventHandlerCallback handler, void *user_data); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Remove event handler by handler ID | ||||
|  * @param handler_id : handler ID, obtained when registering or in the callback. | ||||
|  * @return number of removed handlers | ||||
|  */ | ||||
| int remove_event_handler(uint32_t handler_id); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Handle an event | ||||
|  * @param event : pointer to the event to handle | ||||
|  */ | ||||
| void run_event_handler(Event *event); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Check if hansler exists | ||||
|  * @param handler_id : handler | ||||
|  * @return exists | ||||
|  */ | ||||
| bool event_handler_exists(uint32_t handler_id); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Create a link between two handlers (one direction). | ||||
|  * | ||||
|  * If handler A is linked to handler B, and handler A is removed, | ||||
|  * both handlers will perish. | ||||
|  * | ||||
|  * Make a circle if you need to chain more than two handlers. | ||||
|  * | ||||
|  * @param from : handler A | ||||
|  * @param to   : handler B | ||||
|  * @param reciprocal : link also from B to A | ||||
|  * @return | ||||
|  */ | ||||
| bool chain_event_handler(uint32_t from, uint32_t to, bool reciprocal); | ||||
| @ -0,0 +1,66 @@ | ||||
| #include "event_queue.h" | ||||
| #include "com/debug.h" | ||||
| 
 | ||||
| /** Task queue */ | ||||
| static CircBuf *tq; | ||||
| 
 | ||||
| /** Event queue */ | ||||
| static CircBuf *eq; | ||||
| 
 | ||||
| 
 | ||||
| void queues_init(size_t tq_size, size_t eq_size) | ||||
| { | ||||
| 	tq = cbuf_create(tq_size, sizeof(QueuedTask)); | ||||
| 	eq = cbuf_create(eq_size, sizeof(Event)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool tq_post(void (*handler)(void*), void *arg) | ||||
| { | ||||
| 	QueuedTask task; | ||||
| 	task.handler = handler; | ||||
| 	task.arg = arg; | ||||
| 
 | ||||
| 	bool suc = cbuf_append(tq, &task); | ||||
| 	if (!suc) error("TQ overflow"); | ||||
| 	return suc; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool eq_post(const Event *event) | ||||
| { | ||||
| 	bool suc = cbuf_append(eq, event); | ||||
| 	if (!suc) { | ||||
| 		error("EQ overflow, evt %d", event->type); | ||||
| 	} | ||||
| 	return suc; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool tq_poll_one(void) | ||||
| { | ||||
| 	QueuedTask task; | ||||
| 
 | ||||
| 	// serve all tasks
 | ||||
| 	bool suc = cbuf_pop(tq, &task); | ||||
| 
 | ||||
| 	if (suc) { | ||||
| 		task.handler(task.arg); | ||||
| 	} | ||||
| 
 | ||||
| 	return suc; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void tq_poll(void) | ||||
| { | ||||
| 	// serve all tasks
 | ||||
| 	while (tq_poll_one()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool eq_take(Event *dest) | ||||
| { | ||||
| 	bool suc = cbuf_pop(eq, dest); | ||||
| 	return suc; | ||||
| } | ||||
| @ -0,0 +1,75 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "main.h" | ||||
| #include "utils/circbuf.h" | ||||
| 
 | ||||
| #define TASK_QUEUE_SIZE 64 | ||||
| #define EVENT_QUEUE_SIZE 64 | ||||
| 
 | ||||
| 
 | ||||
| /** Application events */ | ||||
| typedef enum { | ||||
| 	EVENT_ONE // placeholder
 | ||||
| 
 | ||||
| } EventType; | ||||
| 
 | ||||
| 
 | ||||
| /** Event Queue entry */ | ||||
| typedef struct { | ||||
| 	EventType type; | ||||
| 	void *data; | ||||
| } Event; | ||||
| 
 | ||||
| typedef struct { | ||||
| 	void (*handler)(void*); | ||||
| 	void* arg; | ||||
| } QueuedTask; | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set up the task and event queues | ||||
|  * @param tq_size : number of slots in the task queue | ||||
|  * @param eq_size : number of slots in the event queue | ||||
|  */ | ||||
| void queues_init(size_t tq_size, size_t eq_size); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Post a task on the task queue, with arg. | ||||
|  * | ||||
|  * @see tq_post() | ||||
|  * | ||||
|  * @param handler : task function | ||||
|  * @param arg     : argument for the handler | ||||
|  * @return success | ||||
|  */ | ||||
| bool tq_post(void (*handler)(void *), void *arg); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Post an event on the event queue | ||||
|  * @param event : pointer to an event to post; will be copied. | ||||
|  * @return success | ||||
|  */ | ||||
| bool eq_post(const Event *event); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Run all pending tasks on the task queue | ||||
|  */ | ||||
| void tq_poll(void); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Run one pending task on the task queue | ||||
|  * @return true if a task was run. | ||||
|  */ | ||||
| bool tq_poll_one(void); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Take one event off the event queue. | ||||
|  * @param dest : pointer to a destination event variable. | ||||
|  * @return success | ||||
|  */ | ||||
| bool eq_take(Event *dest); | ||||
| @ -0,0 +1,66 @@ | ||||
| #include "colorled.h" | ||||
| #include "utils/timebase.h" | ||||
| 
 | ||||
| #define LONG_DELAY() for (volatile uint32_t __j = 4; __j > 0; __j--) | ||||
| #define SHORT_DELAY() for (volatile uint32_t __j = 1; __j > 0; __j--) | ||||
| 
 | ||||
| static inline | ||||
| __attribute__((always_inline)) | ||||
| void colorled_byte(uint8_t b) | ||||
| { | ||||
| 	for (register volatile uint8_t i = 0; i < 8; i++) { | ||||
| 		COLORLED_GPIO->BSRR = COLORLED_PIN; // set pin high
 | ||||
| 
 | ||||
| 		// duty cycle determines bit value
 | ||||
| 		if (b & 0x80) { | ||||
| 			LONG_DELAY(); | ||||
| 			COLORLED_GPIO->BRR = COLORLED_PIN; // set pin low
 | ||||
| 			SHORT_DELAY(); | ||||
| 		} else { | ||||
| 			SHORT_DELAY(); | ||||
| 			COLORLED_GPIO->BRR = COLORLED_PIN; // set pin low
 | ||||
| 			LONG_DELAY(); | ||||
| 		} | ||||
| 
 | ||||
| 		b <<= 1; // shift to next bit
 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Set one RGB LED color */ | ||||
| void colorled_set(uint32_t rgb) | ||||
| { | ||||
| 	__disable_irq(); // SysTick interrupt when sending data would break the timing
 | ||||
| 
 | ||||
| 	colorled_byte((rgb & 0x00FF00) >> 8); | ||||
| 	colorled_byte((rgb & 0xFF0000) >> 16); | ||||
| 	colorled_byte(rgb & 0x0000FF); | ||||
| 
 | ||||
| 	__enable_irq(); | ||||
| 
 | ||||
| 	delay_us(50); // show
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Set many RGBs */ | ||||
| void colorled_set_many(uint32_t *rgbs, int count) | ||||
| { | ||||
| 	__disable_irq(); | ||||
| 
 | ||||
| 	for (int i = 0; i < count; i++) { | ||||
| 		uint32_t rgb = *rgbs++; | ||||
| 		colorled_byte((rgb & 0x00FF00) >> 8); | ||||
| 		colorled_byte((rgb & 0xFF0000) >> 16); | ||||
| 		colorled_byte(rgb & 0x0000FF); | ||||
| 	} | ||||
| 
 | ||||
| 	__enable_irq(); | ||||
| 
 | ||||
| 	delay_us(50); // show
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void colorled_off(void) | ||||
| { | ||||
| 	colorled_set(RGB_BLACK); | ||||
| } | ||||
| @ -0,0 +1,79 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| /* Includes ------------------------------------------------------------------*/ | ||||
| 
 | ||||
| #include "main.h" | ||||
| 
 | ||||
| /* Exported types ------------------------------------------------------------*/ | ||||
| /* Exported constants --------------------------------------------------------*/ | ||||
| 
 | ||||
| // PB8 - WS2812B data line
 | ||||
| #define COLORLED_GPIO GPIOB | ||||
| #define COLORLED_PIN GPIO_Pin_12 | ||||
| 
 | ||||
| #define RGB_RED     rgb(255,   0,   0) | ||||
| #define RGB_ORANGE  rgb(255, 110,   0) | ||||
| #define RGB_YELLOW  rgb(255, 255,   0) | ||||
| #define RGB_LIME    rgb(160, 255,   0) | ||||
| #define RGB_GREEN   rgb(  0, 255,   0) | ||||
| #define RGB_CYAN    rgb(  0, 255, 120) | ||||
| #define RGB_BLUE    rgb(  0,   0, 255) | ||||
| #define RGB_MAGENTA rgb(255,   0, 255) | ||||
| #define RGB_WHITE   rgb(255, 255, 255) | ||||
| #define RGB_BLACK   rgb(  0,   0,   0) | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Struct for easy manipulation of RGB colors. | ||||
|  * | ||||
|  * Set components in the xrgb.r (etc.) and you will get | ||||
|  * the hex in xrgb.num. | ||||
|  */ | ||||
| typedef union { | ||||
| 
 | ||||
| 	/** Struct for access to individual color components */ | ||||
| 	struct __attribute__((packed)) { | ||||
| 		uint8_t b; | ||||
| 		uint8_t g; | ||||
| 		uint8_t r; | ||||
| 	}; | ||||
| 
 | ||||
| 	/** RGB color as a single uint32_t */ | ||||
| 	uint32_t num; | ||||
| 
 | ||||
| } ws2812_rgb_t; | ||||
| 
 | ||||
| /* Exported macros -----------------------------------------------------------*/ | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Compose an RGB color. | ||||
|  * @param r, g, b - components 0xFF | ||||
|  * @returns integer 0xRRGGBB | ||||
|  */ | ||||
| #define rgb(r, g, b) (((0xFF & (r)) << 16) | ((0xFF & (g)) << 8) | (0xFF & (b))) | ||||
| 
 | ||||
| /* Get components */ | ||||
| #define rgb_r(rgb) (((rgb) >> 16) & 0xFF) | ||||
| #define rgb_g(rgb) (((rgb) >> 8) & 0xFF) | ||||
| #define rgb_b(rgb) ((rgb) & 0xFF) | ||||
| 
 | ||||
| /* Exported functions --------------------------------------------------------*/ | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Turn OFF the rgb LED | ||||
|  */ | ||||
| void colorled_off(void); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set color of a WS2812B | ||||
|  * @param rgb - color 0xRRGGBB | ||||
|  */ | ||||
| void colorled_set(uint32_t rgb); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set color of multiple chained RGB leds | ||||
|  * @param rgbs - array of colors (0xRRGGBB) | ||||
|  * @param count - number of LEDs | ||||
|  */ | ||||
| void colorled_set_many(uint32_t *rgbs, int count); | ||||
| @ -0,0 +1,96 @@ | ||||
| #include "main.h" | ||||
| 
 | ||||
| #include "com_fileio.h" | ||||
| #include "com_iface.h" | ||||
| #include "utils/str_utils.h" | ||||
| 
 | ||||
| // Holding fields for ifaces
 | ||||
| ComIface *debug_iface = NULL; | ||||
| ComIface *data_iface = NULL; | ||||
| 
 | ||||
| 
 | ||||
| // --- File descriptor names ------------------------------
 | ||||
| 
 | ||||
| struct name_fd { | ||||
| 	const char *name; | ||||
| 	const int fd; | ||||
| }; | ||||
| 
 | ||||
| #define NAME_FD_MAP_LEN 1 | ||||
| 
 | ||||
| /** pre-assigned file descriptors for names */ | ||||
| static const struct name_fd name_fd_map[NAME_FD_MAP_LEN] = { | ||||
| 	{FNAME_DLNK, FD_DLNK} | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // --- Syscalls -------------------------------------------
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Write to a file by file descriptor. | ||||
|  * | ||||
|  * @param fd  : open file descriptor | ||||
|  * @param buf : data to write | ||||
|  * @param len : buffer size | ||||
|  * @return number of written bytes | ||||
|  */ | ||||
| int _write(int fd, const char *buf, int len) | ||||
| { | ||||
| 	switch (fd) { | ||||
| 		case FD_STDOUT:	return (int)com_tx_block(debug_iface, buf, (size_t)len); | ||||
| 		case FD_STDERR:	return (int)com_tx_block(debug_iface, buf, (size_t)len); | ||||
| 		case FD_DLNK: return (int)com_tx_block(data_iface, buf, (size_t)len); | ||||
| 		default: | ||||
| 			return 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // For some reason, reading does not work.
 | ||||
| #if 0 | ||||
| /**
 | ||||
|  * @brief Read from a file descriptor | ||||
|  * | ||||
|  * @param fd  : file descriptor | ||||
|  * @param buf : destination buffer | ||||
|  * @param len : number of bytes to read | ||||
|  * @return actual number of read bytes | ||||
|  */ | ||||
| int _read(int fd, char *buf, int len) | ||||
| { | ||||
| 	switch (fd) { | ||||
| 		case FD_STDIN: return com_rx_block(debug_iface, buf, len); | ||||
| 		case FD_ESP: return com_rx_block(esp_iface, buf, len); | ||||
| 		default: | ||||
| 			return 0; | ||||
| 	} | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Open a file by name. | ||||
|  * | ||||
|  * This stub is called by newlib when fopen is used. | ||||
|  * It returns a pre-assigned file descriptor based | ||||
|  * on the name. | ||||
|  * | ||||
|  * @note | ||||
|  * stdout, stderr, stdin are open implicitly | ||||
|  * | ||||
|  * @param name  : file name | ||||
|  * @param flags : file flags (ignored) | ||||
|  * @return file descriptor | ||||
|  */ | ||||
| int _open(const char *name, int flags, ...) | ||||
| { | ||||
| 	(void)flags; | ||||
| 
 | ||||
| 	for (int  i = 0; i < NAME_FD_MAP_LEN; i++) { | ||||
| 		if (streq(name_fd_map[i].name, name)) { | ||||
| 			return name_fd_map[i].fd; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return -1; | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "com_iface.h" | ||||
| 
 | ||||
| /** Debug USART iface */ | ||||
| extern ComIface *debug_iface; | ||||
| 
 | ||||
| /** ESP8266 com iface */ | ||||
| extern ComIface *data_iface; | ||||
| 
 | ||||
| /** Do-nothing iface */ | ||||
| extern ComIface *com_iface_noop; | ||||
| 
 | ||||
| 
 | ||||
| /** File descriptors for use with built-in "files" */ | ||||
| enum { | ||||
| 	FD_STDIN  = 0, | ||||
| 	FD_STDOUT = 1, | ||||
| 	FD_STDERR = 2, | ||||
| 	FD_DLNK = 3, | ||||
| }; | ||||
| 
 | ||||
| #define FNAME_DLNK "dlnk" | ||||
| @ -0,0 +1,179 @@ | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| #include <stdarg.h> | ||||
| 
 | ||||
| #include "com_iface.h" | ||||
| #include "utils/timebase.h" | ||||
| 
 | ||||
| // ---- accessor methods ----------------------
 | ||||
| 
 | ||||
| bool com_rx_rdy(ComIface *iface) | ||||
| { | ||||
| 	return iface->rx_rdy(iface); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool com_tx_rdy(ComIface *iface) | ||||
| { | ||||
| 	return iface->tx_rdy(iface); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool com_tx_done(ComIface *iface) | ||||
| { | ||||
| 	return iface->tx_done(iface); | ||||
| } | ||||
| 
 | ||||
| bool com_tx(ComIface *iface, uint8_t b) | ||||
| { | ||||
| 	return iface->tx(iface, b); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool com_rx(ComIface *iface, uint8_t *b) | ||||
| { | ||||
| 	return iface->rx(iface, (uint8_t*) b); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool com_unrx(ComIface *iface, uint8_t b) | ||||
| { | ||||
| 	if (!iface->unrx) { | ||||
| 		return false; // not all may have it implemented
 | ||||
| 	} | ||||
| 	return iface->unrx(iface, b); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| size_t com_tx_block(ComIface *iface, const void *blob, size_t size) | ||||
| { | ||||
| 	return iface->txb(iface, blob, size); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| size_t com_rx_block(ComIface *iface, void *buf, size_t length) | ||||
| { | ||||
| 	return iface->rxb(iface, buf, length); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void com_poll(ComIface *iface) | ||||
| { | ||||
| 	iface->poll(iface); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool com_rx_char(ComIface *iface, char * c) | ||||
| { | ||||
| 	return iface->rx(iface, (uint8_t*) c); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool com_tx_char(ComIface *iface, const char c) | ||||
| { | ||||
| 	return iface->tx(iface, (uint8_t)c); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Read string, terminate with \0. Returns real read size. */ | ||||
| size_t com_rx_str(ComIface *iface, char* buf, size_t buflen) | ||||
| { | ||||
| 	size_t len = iface->rxb(iface, buf, buflen); | ||||
| 	buf[len] = 0; // zero terminator
 | ||||
| 	return len; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| size_t com_tx_str(ComIface *iface, const char * string) | ||||
| { | ||||
| 	return iface->txb(iface, string, strlen(string)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Wait for incoming byte */ | ||||
| bool com_rx_wait(ComIface *iface, uint16_t timeout) | ||||
| { | ||||
| 	until_timeout(timeout) { | ||||
| 		if (iface->rx_rdy(iface)) return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Wait for tx buf ready */ | ||||
| bool com_tx_rdy_wait(ComIface *iface, uint16_t timeout) | ||||
| { | ||||
| 	until_timeout(timeout) { | ||||
| 		if (iface->tx_rdy(iface)) return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Wait for tx complete */ | ||||
| bool com_tx_done_wait(ComIface *iface, uint16_t timeout) | ||||
| { | ||||
| 	until_timeout(timeout) { | ||||
| 		if (iface->tx_done(iface)) return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void com_printf(ComIface *iface, const char *fmt, ...) | ||||
| { | ||||
| 	if (iface->file == NULL) { | ||||
| 		com_tx_str(iface, "com_printf(), no iface file!\r\n"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	// use the file descriptor attached
 | ||||
| 
 | ||||
| 	va_list va; | ||||
| 	va_start(va, fmt); | ||||
| 	vfprintf(iface->file, fmt, va); | ||||
| 	va_end(va); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void com_vprintf(ComIface *iface, const char *fmt, va_list va) | ||||
| { | ||||
| 	if (iface->file == NULL) { | ||||
| 		com_tx_str(iface, "com_vprintf(), no iface file!\r\n"); | ||||
| 		com_tx_str(iface, fmt); // poor mans fallback
 | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	// use the file descriptor attached
 | ||||
| 	vfprintf(iface->file, fmt, va); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void com_v100_attr_(ComIface *iface, uint8_t count, ...) | ||||
| { | ||||
| 	va_list va; | ||||
| 	va_start(va, count); | ||||
| 
 | ||||
| 	com_tx_char(iface, 27); | ||||
| 	com_tx_char(iface, '['); | ||||
| 
 | ||||
| 	for (int i = 0; i < count; i++) { | ||||
| 		int attr = va_arg(va, int); | ||||
| 
 | ||||
| 		// comma
 | ||||
| 		if (i > 0) com_tx_char(iface, ';'); | ||||
| 
 | ||||
| 		// number
 | ||||
| 		com_printf(iface, "%d", attr); | ||||
| 	} | ||||
| 
 | ||||
| 	com_tx_char(iface, 'm'); | ||||
| 
 | ||||
| 	va_end(va); | ||||
| } | ||||
| @ -0,0 +1,168 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdarg.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| // Implementation-independent UI (command handler)
 | ||||
| 
 | ||||
| typedef struct ComIface_struct ComIface; | ||||
| 
 | ||||
| struct ComIface_struct { | ||||
| 	// --- Variables ---
 | ||||
| 
 | ||||
| 	/** Implementation-specific data object */ | ||||
| 	void *opts; | ||||
| 
 | ||||
| 	/** File descriptor for this stream */ | ||||
| 	FILE *file; | ||||
| 
 | ||||
| 	/**
 | ||||
| 	 * User callback for data ready, triggered on poll(). | ||||
| 	 * Can be null. | ||||
| 	 */ | ||||
| 	void (*rx_callback)(ComIface *iface); | ||||
| 
 | ||||
| 	// --- Interface methods ---
 | ||||
| 
 | ||||
| 	/** Data ready to be read (RX buffer Not Empty) */ | ||||
| 	bool (*rx_rdy)(ComIface *iface); | ||||
| 
 | ||||
| 	/** Free space in TX buffer */ | ||||
| 	bool (*tx_rdy)(ComIface *iface); | ||||
| 
 | ||||
| 	/** Everything sent */ | ||||
| 	bool (*tx_done)(ComIface *iface); | ||||
| 
 | ||||
| 	/** Send 1 byte. Blocking, timeout after some time. */ | ||||
| 	bool (*tx)(ComIface *iface, uint8_t b); | ||||
| 
 | ||||
| 	/** Read one byte. False on failure. Blocking, timeout after some time. */ | ||||
| 	bool (*rx)(ComIface *iface, uint8_t *b); | ||||
| 
 | ||||
| 	/** Unreceive one byte. False on failure. */ | ||||
| 	bool (*unrx)(ComIface *iface, uint8_t b); | ||||
| 
 | ||||
| 	/** Send a binary block. False on failure. */ | ||||
| 	size_t (*txb)(ComIface *iface, const void *blob, size_t size); | ||||
| 
 | ||||
| 	/** Read a binary block. Returns real read length.  Blocking, timeout after some time. */ | ||||
| 	size_t (*rxb)(ComIface *iface, void *buf, size_t length); | ||||
| 
 | ||||
| 	/** Poll for changes */ | ||||
| 	void (*poll)(ComIface *iface); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // ---- Functions for working with iface --------------
 | ||||
| 
 | ||||
| /** Wait for incoming byte */ | ||||
| bool com_rx_wait(ComIface *iface, uint16_t timeout); | ||||
| 
 | ||||
| /** Wait for free space in tx buffer */ | ||||
| bool com_tx_rdy_wait(ComIface *iface, uint16_t timeout); | ||||
| 
 | ||||
| /** Wait for tx complete */ | ||||
| bool com_tx_done_wait(ComIface *iface, uint16_t timeout); | ||||
| 
 | ||||
| /** Check if there's data in the receive buffer */ | ||||
| bool com_rx_rdy(ComIface *iface); | ||||
| 
 | ||||
| /** Check if transmit buffer can accept data */ | ||||
| bool com_tx_rdy(ComIface *iface); | ||||
| 
 | ||||
| /** Check if transmit buffer is empty */ | ||||
| bool com_tx_done(ComIface *iface); | ||||
| 
 | ||||
| /** Send 1 byte */ | ||||
| bool com_tx(ComIface *iface, uint8_t b); | ||||
| 
 | ||||
| /** Read one byte. False on failure. Fails on timeout. */ | ||||
| bool com_rx(ComIface *iface, uint8_t *b); | ||||
| 
 | ||||
| /** Unrx one byte. False on failure. */ | ||||
| bool com_unrx(ComIface *iface, uint8_t b); | ||||
| 
 | ||||
| /** Send a binary blob. False on failure. Fails on timeout. */ | ||||
| size_t com_tx_block(ComIface *iface, const void *blob, size_t size); | ||||
| 
 | ||||
| /** Read a blob. Returns real read length. Stops on timeout. */ | ||||
| size_t com_rx_block(ComIface *iface, void *buf, size_t length); | ||||
| 
 | ||||
| /** Poll for changes */ | ||||
| void com_poll(ComIface *iface); | ||||
| 
 | ||||
| /** Send 1 char */ | ||||
| bool com_tx_char(ComIface *iface, char c); | ||||
| 
 | ||||
| /** Read one char. False on failure. Fails on timeout. */ | ||||
| bool com_rx_char(ComIface *iface, char *c); | ||||
| 
 | ||||
| /** Send a string. False on failure. */ | ||||
| size_t com_tx_str(ComIface *iface, const char *str); | ||||
| 
 | ||||
| /** Read a string. Returns real read length. Stops on timeout. */ | ||||
| size_t com_rx_str(ComIface *iface, char *buf, size_t length); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Printf to a serial interface. | ||||
|  * Max length is 255 chars. | ||||
|  */ | ||||
| void com_printf(ComIface *iface, const char *fmt, ...) | ||||
| 	__attribute__ ((format (printf, 2, 3))); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Printf to a serial interface, with va_list. | ||||
|  */ | ||||
| void com_vprintf(ComIface *iface, const char *fmt, va_list va); | ||||
| 
 | ||||
| 
 | ||||
| // ---- VT100/ANSI color support -------------
 | ||||
| 
 | ||||
| /** ANSI formatting attributes */ | ||||
| typedef enum { | ||||
| 	// Non-colour Attributes
 | ||||
| 	FMT_RESET = 0, // Reset all attributes
 | ||||
| 	FMT_BRIGHT = 1, // Bright
 | ||||
| 	FMT_DIM = 2, // Dim
 | ||||
| 	FMT_UNDER = 4, // Underscore
 | ||||
| 	FMT_BLINK = 5, // Blink
 | ||||
| 	FMT_INVERS = 7, // Reverse
 | ||||
| 	FMT_HIDDEN = 8, // Hidden
 | ||||
| 	FMT_ITALIC = 16, // Italic font
 | ||||
| 	FMT_FAINT  = 32, // Faint color
 | ||||
| 
 | ||||
| 	// Foreground Colours
 | ||||
| 	FMT_BLACK = 30, // Black
 | ||||
| 	FMT_RED = 31, // Red
 | ||||
| 	FMT_GREEN = 32, // Green
 | ||||
| 	FMT_YELLOW = 33, // Yellow
 | ||||
| 	FMT_BLUE = 34, // Blue
 | ||||
| 	FMT_MAGENTA = 35, // Magenta
 | ||||
| 	FMT_CYAN = 36, // Cyan
 | ||||
| 	FMT_WHITE = 37, // White
 | ||||
| 
 | ||||
| 	// Background Colours
 | ||||
| 	FMT_BLACK_BG = 40, // Black
 | ||||
| 	FMT_RED_BG = 41, // Red
 | ||||
| 	FMT_GREEN_BG = 42, // Green
 | ||||
| 	FMT_YELLOW_BG = 43, // Yellow
 | ||||
| 	FMT_BLUE_BG = 44, // Blue
 | ||||
| 	FMT_MAGENTA_BG = 45, // Magenta
 | ||||
| 	FMT_CYAN_BG = 46, // Cyan
 | ||||
| 	FMT_WHITE_BG = 47, // White
 | ||||
| } ANSI_attr_t; | ||||
| 
 | ||||
| #define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1) | ||||
| #define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N | ||||
| 
 | ||||
| #define com_v100_attr(iface, ...) com_v100_attr_(iface, VA_NUM_ARGS(__VA_ARGS__), __VA_ARGS__) | ||||
| 
 | ||||
| /**
 | ||||
|  * Send formatting code to a com interface | ||||
|  */ | ||||
| void com_v100_attr_(ComIface *iface, uint8_t count, ...); | ||||
| @ -0,0 +1,48 @@ | ||||
| #include "datalink.h" | ||||
| #include "com_fileio.h" | ||||
| #include "debug.h" | ||||
| #include "com_fileio.h" | ||||
| 
 | ||||
| SBMP_Endpoint *dlnk_ep; | ||||
| 
 | ||||
| static void dlnk_tx(uint8_t b); | ||||
| static void dlnk_rx_bridge(ComIface *iface); | ||||
| 
 | ||||
| /** Set up the datalink */ | ||||
| void dlnk_init(void) | ||||
| { | ||||
| 	// Allocate & setup the endpoint
 | ||||
| 	dlnk_ep = sbmp_ep_init(NULL, NULL, 100, dlnk_rx, dlnk_tx); | ||||
| 	sbmp_ep_seed_session(dlnk_ep, 0x3FFF); // just in case arbitration fails
 | ||||
| 	sbmp_ep_enable(dlnk_ep, true); | ||||
| 
 | ||||
| 	sbmp_ep_init_listeners(dlnk_ep, NULL, 4); // really don' need many here..
 | ||||
| 
 | ||||
| 	data_iface->rx_callback = dlnk_rx_bridge; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Bridge between USART subsystem's Rx buffer, and the SBMP driver | ||||
|  * | ||||
|  * Called if bytes are received in the USART buffer, | ||||
|  * and the USART subsystem is polled. | ||||
|  */ | ||||
| static void dlnk_rx_bridge(ComIface *iface) | ||||
| { | ||||
| 	uint8_t b; | ||||
| 	while (com_rx(iface, &b)) { | ||||
| 		SBMP_RxStatus st = sbmp_ep_receive(dlnk_ep, b); | ||||
| 
 | ||||
| 		// If byte was not accepted, try to put it back into the buffer
 | ||||
| 		if (st == SBMP_RX_BUSY || st == SBMP_RX_DISABLED) { | ||||
| 			com_unrx(iface, b); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /** Datalink Tx func */ | ||||
| static void dlnk_tx(uint8_t b) | ||||
| { | ||||
| 	com_tx(data_iface, b); | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| /**
 | ||||
|  * SBMP setup & funcs | ||||
|  */ | ||||
| 
 | ||||
| #include "main.h" | ||||
| #include <sbmp.h> | ||||
| 
 | ||||
| #define DG_REQUEST_RAW 40 // request raw vector. Sample count [u16], Frequency [u32]
 | ||||
| #define DG_REQUEST_FFT 41 // request fft vector. Sample count [u16], Frequency [u32]. Result - count/2 bins. Count must be 2^n, 16..2048
 | ||||
| #define DG_REQUEST_STORE_REF 42 // calculate signal signature & store for comparing
 | ||||
| #define DG_REQUEST_COMPARE_REF 43 | ||||
| // wifi status & control
 | ||||
| #define DG_SETMODE_AP 44 // request AP mode (AP button pressed)
 | ||||
| #define DG_WPS_START 45 // start WPS
 | ||||
| #define DG_WIFI_STATUS 46 // WiFi status report
 | ||||
| #define DG_REQUEST_STM_VERSION 47 // Get acquisition module firmware version
 | ||||
| 
 | ||||
| 
 | ||||
| extern SBMP_Endpoint *dlnk_ep; | ||||
| 
 | ||||
| void dlnk_init(void); | ||||
| 
 | ||||
| /** Received datagram handler */ | ||||
| extern void dlnk_rx(SBMP_Datagram *dg); | ||||
| @ -0,0 +1,108 @@ | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| #include <stdarg.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "com_iface.h" | ||||
| 
 | ||||
| #include "utils/timebase.h" | ||||
| 
 | ||||
| #include "debug.h" | ||||
| 
 | ||||
| 
 | ||||
| void dbg_printf(const char *fmt, ...) | ||||
| { | ||||
| 	va_list va; | ||||
| 	va_start(va, fmt); | ||||
| 	com_vprintf(debug_iface, fmt, va); //	vsnprintf(strbuf, DBG_BUF_LEN, fmt, va);
 | ||||
| 	va_end(va); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void dbg_va_base(const char *fmt, const char *tag, va_list va) | ||||
| { | ||||
| 	ms_time_t now = ms_now(); | ||||
| 	uint32_t secs = now / 1000; | ||||
| 	uint32_t ms = now % 1000; | ||||
| 
 | ||||
| 	com_printf(debug_iface, "%4"PRIu32".%03"PRIu32" ", secs, ms); | ||||
| 
 | ||||
| 	dbg_raw(tag); | ||||
| 
 | ||||
| 	com_vprintf(debug_iface, fmt, va); | ||||
| 	dbg_raw(DEBUG_EOL); | ||||
| } | ||||
| 
 | ||||
| /** Print a log message with an INFO tag and newline (ONLY FOR BANNER - always shown) */ | ||||
| void banner_info(const char *fmt, ...) | ||||
| { | ||||
| 	com_v100_attr(debug_iface, FMT_GREEN); | ||||
| 
 | ||||
| 	va_list va; | ||||
| 	va_start(va, fmt); | ||||
| 	dbg_va_base(fmt, DEBUG_TAG_INFO, va); | ||||
| 	va_end(va); | ||||
| 
 | ||||
| 	com_v100_attr(debug_iface, FMT_RESET); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #if VERBOSE_LOGGING | ||||
| 
 | ||||
| /** Print a log message with a DEBUG tag and newline */ | ||||
| void dbg(const char *fmt, ...) | ||||
| { | ||||
| 	va_list va; | ||||
| 	va_start(va, fmt); | ||||
| 	dbg_va_base(fmt, DEBUG_TAG_BASE, va); | ||||
| 	va_end(va); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Print a log message with an INFO tag and newline */ | ||||
| void info(const char *fmt, ...) __attribute__((alias("banner_info"))); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /** Print a log message with an INFO tag and newline */ | ||||
| void banner(const char *fmt, ...) | ||||
| { | ||||
| 	com_v100_attr(debug_iface, FMT_GREEN, FMT_BRIGHT); | ||||
| 
 | ||||
| 	va_list va; | ||||
| 	va_start(va, fmt); | ||||
| 	dbg_va_base(fmt, DEBUG_TAG_INFO, va); | ||||
| 	va_end(va); | ||||
| 
 | ||||
| 	com_v100_attr(debug_iface, FMT_RESET); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Print a log message with a warning tag and newline */ | ||||
| void warn(const char *fmt, ...) | ||||
| { | ||||
| 	com_v100_attr(debug_iface, FMT_YELLOW, FMT_BRIGHT); | ||||
| 
 | ||||
| 	va_list va; | ||||
| 	va_start(va, fmt); | ||||
| 	dbg_va_base(fmt, DEBUG_TAG_WARN, va); | ||||
| 	va_end(va); | ||||
| 
 | ||||
| 	com_v100_attr(debug_iface, FMT_RESET); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Print a log message with an ERROR tag and newline */ | ||||
| void error(const char *fmt, ...) | ||||
| { | ||||
| 	com_v100_attr(debug_iface, FMT_RED, FMT_BRIGHT); | ||||
| 
 | ||||
| 	va_list va; | ||||
| 	va_start(va, fmt); | ||||
| 	dbg_va_base(fmt, DEBUG_TAG_ERROR, va); | ||||
| 	va_end(va); | ||||
| 
 | ||||
| 	com_v100_attr(debug_iface, FMT_RESET); | ||||
| } | ||||
| @ -0,0 +1,73 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "main.h" | ||||
| #include "com_iface.h" | ||||
| #include "com_fileio.h" | ||||
| 
 | ||||
| #include "bus/event_queue.h" | ||||
| 
 | ||||
| // helper to mark printf functions
 | ||||
| #define PRINTF_LIKE __attribute__((format(printf, 1, 2))) | ||||
| 
 | ||||
| #define DBG_BUF_LEN 256 | ||||
| 
 | ||||
| #define ESCAPE_DEBUG_MESSAGES 1 | ||||
| 
 | ||||
| 
 | ||||
| // formatting symbols
 | ||||
| #define DEBUG_EOL "\r\n" | ||||
| #define DEBUG_TAG_WARN  "[W] " | ||||
| #define DEBUG_TAG_ERROR "[E] " | ||||
| #define DEBUG_TAG_BASE  "[ ] " | ||||
| #define DEBUG_TAG_INFO  "[i] " | ||||
| 
 | ||||
| 
 | ||||
| /** Print a log message with no tag and no newline */ | ||||
| void dbg_printf(const char *fmt, ...) PRINTF_LIKE; | ||||
| 
 | ||||
| /** Print via va_list */ | ||||
| void dbg_va_base(const char *fmt, const char *tag, va_list va); | ||||
| 
 | ||||
| /** Print a string to the debug interface (length not limited) */ | ||||
| static inline void dbg_raw(const char *str) | ||||
| { | ||||
| 	com_tx_str(debug_iface, str); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Print a char to the debug interface */ | ||||
| static inline void dbg_raw_c(char c) | ||||
| { | ||||
| 	com_tx(debug_iface, (uint8_t)c); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #if VERBOSE_LOGGING | ||||
| 
 | ||||
| /** Print a log message with a "debug" tag and newline */ | ||||
| void dbg(const char *fmt, ...) PRINTF_LIKE; | ||||
| 
 | ||||
| 
 | ||||
| /** Print a log message with an "info" tag and newline */ | ||||
| void info(const char *fmt, ...) PRINTF_LIKE; | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
| #define dbg(fmt, ...) | ||||
| #define info(fmt, ...) | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| /** Print a log message with an "info" tag and newline */ | ||||
| void banner_info(const char *fmt, ...) PRINTF_LIKE; | ||||
| 
 | ||||
| /** Print a log message with a "banner" tag and newline */ | ||||
| void banner(const char *fmt, ...) PRINTF_LIKE; | ||||
| 
 | ||||
| /** Print a log message with a "warning" tag and newline */ | ||||
| void warn(const char *fmt, ...) PRINTF_LIKE; | ||||
| 
 | ||||
| 
 | ||||
| /** Print a log message with an "error" tag and newline */ | ||||
| void error(const char *fmt, ...) PRINTF_LIKE; | ||||
| @ -0,0 +1,91 @@ | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #include "iface_noop.h" | ||||
| #include "com_fileio.h" | ||||
| #include "malloc_safe.h" | ||||
| 
 | ||||
| // ==== NOOP com driver ====
 | ||||
| 
 | ||||
| static bool if_rx_rdy(ComIface *iface) | ||||
| { | ||||
| 	(void)iface; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static bool if_tx_rdy(ComIface *iface) | ||||
| { | ||||
| 	(void)iface; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool if_tx_done(ComIface *iface) | ||||
| { | ||||
| 	(void)iface; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool if_rx(ComIface *iface, uint8_t *b) | ||||
| { | ||||
| 	(void)iface; | ||||
| 	(void)b; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static bool if_unrx(ComIface *iface, uint8_t b) | ||||
| { | ||||
| 	(void)iface; | ||||
| 	(void)b; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static bool if_tx(ComIface *iface, uint8_t b) | ||||
| { | ||||
| 	(void)iface; | ||||
| 	(void)b; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static size_t if_rxb(ComIface *iface, void *buf, size_t buflen) | ||||
| { | ||||
| 	(void)iface; | ||||
| 	(void)buf; | ||||
| 	(void)buflen; | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static size_t if_txb(ComIface *iface, const void *blob, size_t size) | ||||
| { | ||||
| 	(void)iface; | ||||
| 	(void)blob; | ||||
| 
 | ||||
| 	return size; | ||||
| } | ||||
| 
 | ||||
| static void if_poll(ComIface *iface) | ||||
| { | ||||
| 	(void)iface; | ||||
| } | ||||
| 
 | ||||
| ComIface *com_noop_init(void) | ||||
| { | ||||
| 	ComIface *iface = malloc_s(sizeof(ComIface)); | ||||
| 
 | ||||
| 	iface->rx_rdy = if_rx_rdy; | ||||
| 	iface->tx_rdy = if_tx_rdy; | ||||
| 	iface->tx_done = if_tx_done; | ||||
| 
 | ||||
| 	iface->rx = if_rx; | ||||
| 	iface->unrx = if_unrx; | ||||
| 	iface->tx = if_tx; | ||||
| 
 | ||||
| 	iface->rxb = if_rxb; | ||||
| 	iface->txb = if_txb; | ||||
| 
 | ||||
| 	iface->poll = if_poll; | ||||
| 	iface->rx_callback = NULL; | ||||
| 
 | ||||
| 	return iface; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| /**
 | ||||
|  * NOOP iface works like /dev/null | ||||
|  */ | ||||
| 
 | ||||
| #include "com_iface.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Initialize a do-nothing interface. | ||||
|  * @param iface : iface pointer. If NULL, it'll be allocated. | ||||
|  * @return the iface pointer. | ||||
|  */ | ||||
| ComIface *com_noop_init(void); | ||||
| @ -0,0 +1,328 @@ | ||||
| #include "main.h" | ||||
| #include "malloc_safe.h" | ||||
| #include "iface_usart.h" | ||||
| 
 | ||||
| #include "utils/timebase.h" | ||||
| #include "utils/circbuf.h" | ||||
| 
 | ||||
| #include "com/com_fileio.h" | ||||
| #include "com/debug.h" | ||||
| 
 | ||||
| #include "bus/event_queue.h" | ||||
| 
 | ||||
| 
 | ||||
| #define DEF_WAIT_TO_MS 5 | ||||
| 
 | ||||
| 
 | ||||
| typedef struct  { | ||||
| 	uint16_t wait_timeout; | ||||
| 	USART_TypeDef *dev; | ||||
| 	CircBuf *rxbuf; // allocated receive buffer
 | ||||
| 	CircBuf *txbuf; // allocated transmit buffer, can be NULL -> no buffer
 | ||||
| } usart_opts; | ||||
| 
 | ||||
| #define opts(iface) ((usart_opts *)(iface)->opts) | ||||
| 
 | ||||
| // ---- Instances ----
 | ||||
| // (needed for async rx/tx with interrupts)
 | ||||
| 
 | ||||
| static ComIface *usart1_iface = NULL; | ||||
| static ComIface *usart2_iface = NULL; | ||||
| static ComIface *usart3_iface = NULL; | ||||
| 
 | ||||
| // -------------------
 | ||||
| 
 | ||||
| 
 | ||||
| /** Check if data is ready for reading */ | ||||
| static bool if_rx_rdy(ComIface *iface) | ||||
| { | ||||
| 	CircBuf *buf = opts(iface)->rxbuf; | ||||
| 
 | ||||
| 	return !cbuf_empty(buf); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Check if sending is done */ | ||||
| static bool if_tx_rdy(ComIface *iface) | ||||
| { | ||||
| 	CircBuf *buf = opts(iface)->txbuf; | ||||
| 
 | ||||
| 	if (buf == NULL) { | ||||
| 		// non-buffered mode
 | ||||
| 		USART_TypeDef* USARTx = opts(iface)->dev; | ||||
| 		return (USARTx->SR & USART_SR_TXE); | ||||
| 	} | ||||
| 
 | ||||
| 	return !cbuf_full(buf); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Check if sending is done */ | ||||
| static bool if_tx_done(ComIface *iface) | ||||
| { | ||||
| 	CircBuf *buf = opts(iface)->txbuf; | ||||
| 	USART_TypeDef* USARTx = opts(iface)->dev; | ||||
| 
 | ||||
| 	// NULL buffer is considered empty
 | ||||
| 	return cbuf_empty(buf) && (USARTx->SR & USART_SR_TC); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Read one byte (wait for it). False on error. */ | ||||
| static bool if_rx(ComIface *iface, uint8_t *b) | ||||
| { | ||||
| 	// wait for data in the buffer
 | ||||
| 	if (!com_rx_wait(iface, opts(iface)->wait_timeout) || b == NULL) { | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	// read from buffer
 | ||||
| 	cbuf_pop(opts(iface)->rxbuf, b); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Try to unreceive a byte. False on error. */ | ||||
| static bool if_unrx(ComIface *iface, uint8_t b) | ||||
| { | ||||
| 	// push back
 | ||||
| 	return cbuf_push(opts(iface)->rxbuf, &b); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Send one byte (waits for tx) */ | ||||
| static bool if_tx(ComIface *iface, uint8_t b) | ||||
| { | ||||
| 	usart_opts *uopts = opts(iface); | ||||
| 	USART_TypeDef* USARTx = uopts->dev; | ||||
| 
 | ||||
| 	if (uopts->txbuf == NULL) { | ||||
| 
 | ||||
| 		// Blocking tx mode
 | ||||
| 		until_timeout(uopts->wait_timeout) { | ||||
| 			if (USARTx->SR & USART_SR_TXE) { | ||||
| 				USARTx->DR = b; | ||||
| 				return true; // success
 | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return false; | ||||
| 
 | ||||
| 	} else { | ||||
| 
 | ||||
| 		// Buffered mode
 | ||||
| 
 | ||||
| 		// wait for free space in the buffer
 | ||||
| 		bool ready = com_tx_rdy_wait(iface, uopts->wait_timeout); | ||||
| 
 | ||||
| 		if (ready) { | ||||
| 			// append to the buffer
 | ||||
| 			cbuf_append(uopts->txbuf, &b); | ||||
| 		} | ||||
| 
 | ||||
| 		// regardless ready state, start Tx if there are bytes
 | ||||
| 		// (should fix a bug where full buffer was never emptied)
 | ||||
| 
 | ||||
| 		// If TXE (send buffer empty), start sending the buffer
 | ||||
| 		// Otherwise, IRQ chain is in progress.
 | ||||
| 		if (USARTx->SR & USART_SR_TXE) { | ||||
| 			USART_ITConfig(USARTx, USART_IT_TXE, ENABLE); // start tx chain
 | ||||
| 		} | ||||
| 
 | ||||
| 		return ready; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Read a blob. Returns real read size */ | ||||
| static size_t if_rxb(ComIface *iface, void *buf, size_t buflen) | ||||
| { | ||||
| 	if (buf == NULL) return false; | ||||
| 	//if (!com_rx_wait(iface, opts(iface)->wait_timeout)) return 0;
 | ||||
| 
 | ||||
| 	uint8_t* byteptr = (uint8_t*)buf; | ||||
| 	for (size_t i = 0; i < buflen; i++) { | ||||
| 		if (!if_rx(iface, byteptr++)) return i; | ||||
| 	} | ||||
| 
 | ||||
| 	return buflen; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Send a binary blob. False on error. */ | ||||
| static size_t if_txb(ComIface *iface, const void *blob, size_t size) | ||||
| { | ||||
| 	if (blob == NULL) return false; | ||||
| 	//if (!com_tx_rdy_wait(iface, opts(iface)->wait_timeout)) return false;
 | ||||
| 
 | ||||
| 	const uint8_t* byteptr = (const uint8_t*)blob; | ||||
| 	for (size_t i = 0; i < size; i++) { | ||||
| 		if (!if_tx(iface, *byteptr++)) return i; | ||||
| 	} | ||||
| 
 | ||||
| 	return size; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Check for incoming data */ | ||||
| static void if_poll(ComIface *iface) | ||||
| { | ||||
| 	if (if_rx_rdy(iface)) { | ||||
| 		// call user cb
 | ||||
| 		if (iface->rx_callback) { | ||||
| 			iface->rx_callback(iface); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // ---- Init interface ----
 | ||||
| 
 | ||||
| //void usart_iface_set_baudrate(ComIface *iface, uint32_t baud)
 | ||||
| //{
 | ||||
| //	USART_TypeDef* USARTx = opts(iface)->dev;
 | ||||
| //
 | ||||
| //	USART_SetBaudrate(USARTx, baud);
 | ||||
| //}
 | ||||
| 
 | ||||
| 
 | ||||
| /* public */ | ||||
| ComIface *usart_iface_init(USART_TypeDef* USARTx, uint32_t baud, size_t rxbuf_len, size_t txbuf_len) | ||||
| { | ||||
| 	assert_param(IS_USART_BAUDRATE(baud)); | ||||
| 	assert_param(rxbuf_len > 0); | ||||
| 
 | ||||
| 	ComIface *iface = malloc_s(sizeof(ComIface)); | ||||
| 
 | ||||
| 	// --- setup device specific iface data ---
 | ||||
| 
 | ||||
| 	// Allocate the opts config object
 | ||||
| 	iface->opts = malloc_s(sizeof(usart_opts)); | ||||
| 
 | ||||
| 	// Set device ID
 | ||||
| 	opts(iface)->dev = USARTx; | ||||
| 	opts(iface)->wait_timeout = DEF_WAIT_TO_MS; | ||||
| 
 | ||||
| 	// Initialize the rx/tx buffers (malloc's the array)
 | ||||
| 	opts(iface)->rxbuf = cbuf_create(rxbuf_len, 1); | ||||
| 
 | ||||
| 	// zero-length TX buffer -> blocking Tx
 | ||||
| 	opts(iface)->txbuf = (txbuf_len == 0) ? NULL : cbuf_create(txbuf_len, 1); | ||||
| 
 | ||||
| 	// --- driver instance ---
 | ||||
| 
 | ||||
| 	iface->rx_rdy = if_rx_rdy; | ||||
| 	iface->tx_rdy = if_tx_rdy; | ||||
| 	iface->tx_done = if_tx_done; | ||||
| 
 | ||||
| 	iface->rx = if_rx; | ||||
| 	iface->unrx = if_unrx; | ||||
| 	iface->tx = if_tx; | ||||
| 
 | ||||
| 	iface->txb = if_txb; | ||||
| 	iface->rxb = if_rxb; | ||||
| 
 | ||||
| 	iface->poll = if_poll; | ||||
| 
 | ||||
| 	iface->rx_callback = NULL; // user can replace
 | ||||
| 
 | ||||
| 
 | ||||
| 	/* enable peripehral clock, assign to holder var, enable IRQ */ | ||||
| 	IRQn_Type irqn; | ||||
| 
 | ||||
| 	if (USARTx == USART1) { | ||||
| 		// USART1
 | ||||
| 		usart1_iface = iface; | ||||
| 		irqn = USART1_IRQn; | ||||
| 		RCC->APB2ENR |= RCC_APB2ENR_USART1EN; | ||||
| 
 | ||||
| 	} else if (USARTx == USART2) { | ||||
| 		// USART2
 | ||||
| 		usart2_iface = iface; | ||||
| 		irqn = USART2_IRQn; | ||||
| 		RCC->APB1ENR |= RCC_APB1ENR_USART2EN; | ||||
| 
 | ||||
| 	} else { | ||||
| 		// USART3
 | ||||
| 		usart3_iface = iface; | ||||
| 		irqn = USART3_IRQn; | ||||
| 		RCC->APB1ENR |= RCC_APB1ENR_USART3EN; | ||||
| 	} | ||||
| 
 | ||||
| 	// Enable IRQ
 | ||||
| 	NVIC_EnableIRQ(irqn); | ||||
| 
 | ||||
| 	// lower priority than SysTick, but high
 | ||||
| 	NVIC_SetPriority(irqn, 1); // function does the shifting
 | ||||
| 
 | ||||
| 
 | ||||
| 	/* Setup UART parameters. */ | ||||
| 	USART_InitTypeDef usart; | ||||
| 	USART_StructInit(&usart); | ||||
| 	usart.USART_BaudRate = baud; | ||||
| 	USART_Init(USARTx, &usart); | ||||
| 
 | ||||
| 	/* Enable */ | ||||
| 	USART_Cmd(USARTx, ENABLE); | ||||
| 
 | ||||
| 	// Enable Rx interrupt
 | ||||
| 	USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE); // disable IRQ
 | ||||
| 
 | ||||
| 	return iface; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // ---- IRQ handlers for chained writing and rx ----
 | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Common USART irq handler | ||||
|  * @param iface the interface at which the event occured | ||||
|  */ | ||||
| static void usart_iface_irq_base(ComIface* iface) | ||||
| { | ||||
| 	USART_TypeDef* USARTx = opts(iface)->dev; | ||||
| 
 | ||||
| 	if (USARTx->SR & USART_SR_RXNE) { | ||||
| 		// Byte received.
 | ||||
| 		uint8_t rxb = USARTx->DR & 0xFF; | ||||
| 		if (!cbuf_append(opts(iface)->rxbuf, &rxb)) { | ||||
| 			// Buffer overflow
 | ||||
| 			// Can't print debug msg, can cause deadlock
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (USARTx->SR & USART_SR_TXE) { | ||||
| 		// Byte sent.
 | ||||
| 		uint8_t txb; | ||||
| 
 | ||||
| 		// Send next byte (if buffer is NULL, cbuf_pop also returns false)
 | ||||
| 		if (cbuf_pop(opts(iface)->txbuf, &txb)) { | ||||
| 			USARTx->DR = txb; // send data
 | ||||
| 		} else { | ||||
| 			USART_ITConfig(USARTx, USART_IT_TXE, DISABLE); // disable IRQ
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Clear ORE flag if set
 | ||||
| 	USART_ClearFlag(USARTx, USART_FLAG_ORE); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void USART1_IRQHandler(void) | ||||
| { | ||||
| 	usart_iface_irq_base(usart1_iface); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void USART2_IRQHandler(void) | ||||
| { | ||||
| 	usart_iface_irq_base(usart2_iface); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void USART3_IRQHandler(void) | ||||
| { | ||||
| 	usart_iface_irq_base(usart3_iface); | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "main.h" | ||||
| #include "com_iface.h" | ||||
| 
 | ||||
| // Hardware USART.
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Init an USART interface. Caller must configure GPIO's & AF manually. | ||||
|  * @param USARTx  device | ||||
|  * @param baud    baud rate (ex.: 9600) | ||||
|  * @param rxbuf_len  receive buffer size, must be > 0 | ||||
|  * @param txbuf_len  send buffer size. If 0, tx is blocking. | ||||
|  * @return the iface structure | ||||
|  */ | ||||
| ComIface *usart_iface_init(USART_TypeDef* USARTx, uint32_t baud, size_t rxbuf_len, size_t txbuf_len); | ||||
| 
 | ||||
| ///** Set baud rate */
 | ||||
| //void usart_iface_set_baudrate(ComIface *iface, uint32_t baud);
 | ||||
| @ -0,0 +1,94 @@ | ||||
| #include "display.h" | ||||
| #include "com/debug.h" | ||||
| #include "utils/timebase.h" | ||||
| #include "utils/meanbuf.h" | ||||
| 
 | ||||
| #include <math.h> | ||||
| 
 | ||||
| #define PIXEL_COUNT 30 | ||||
| 
 | ||||
| static ws2812_rgb_t pixels[PIXEL_COUNT] = {}; | ||||
| 
 | ||||
| static MeanBuf *mb; | ||||
| 
 | ||||
| void display_show(void) | ||||
| { | ||||
| 	colorled_set_many((uint32_t*) pixels, PIXEL_COUNT); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void handle_sonar_value(float mm) | ||||
| { | ||||
| 	for (int i = PIXEL_COUNT-1; i > 0; i--) { | ||||
| 		pixels[i].num = pixels[i-1].num; | ||||
| 	} | ||||
| 
 | ||||
| 	float x = mm/5.0f; | ||||
| 	if (x>255) x = 255; | ||||
| 
 | ||||
| 	pixels[0].r = 255-x; | ||||
| 	pixels[0].b = x; | ||||
| 
 | ||||
| 	display_show(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void show(void*arg) | ||||
| { | ||||
| 	(void)arg; | ||||
| 
 | ||||
| 	handle_sonar_value(meanbuf_current(mb)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void sonar(void* arg) | ||||
| { | ||||
| 	(void)arg; | ||||
| 
 | ||||
| 	info("Sonar"); | ||||
| 
 | ||||
| 	GPIOB->BSRR = GPIO_Pin_13; | ||||
| 	delay_us(10); | ||||
| 	GPIOB->BRR = GPIO_Pin_13; | ||||
| 
 | ||||
| 	// wait for response
 | ||||
| 
 | ||||
| 	bool suc = false; | ||||
| 	until_timeout(50) { | ||||
| 		if((GPIOB->IDR & (1 << 14)) != 0) { | ||||
| 			suc = true; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (!suc) { | ||||
| 		dbg("Not suc"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	uint32_t cnt = 0; | ||||
| 	until_timeout(50) { | ||||
| 		if((GPIOB->IDR & (1 << 14)) == 0) break; | ||||
| 		cnt++; | ||||
| 	} | ||||
| 
 | ||||
| 	float t = cnt / 11.2f; | ||||
| 
 | ||||
| 	meanbuf_add(mb, t); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void display_init(void) | ||||
| { | ||||
| 	mb = meanbuf_create(10); | ||||
| 
 | ||||
| 	for (int i = 0; i < PIXEL_COUNT; i++) { | ||||
| 		pixels[i].num = rgb(0, 0, 255); | ||||
| 	} | ||||
| 
 | ||||
| 	display_show(); | ||||
| 
 | ||||
| 	add_periodic_task(sonar, NULL, 50, true); | ||||
| 
 | ||||
| 	add_periodic_task(show, NULL, 50, true); | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| #ifndef DISPLAY_H | ||||
| #define DISPLAY_H | ||||
| 
 | ||||
| #include "main.h" | ||||
| #include "colorled.h" | ||||
| 
 | ||||
| void display_show(void); | ||||
| 
 | ||||
| void display_init(void); | ||||
| 
 | ||||
| #endif // DISPLAY_H
 | ||||
| @ -0,0 +1,125 @@ | ||||
| #include "hw_init.h" | ||||
| 
 | ||||
| #include "com/iface_usart.h" | ||||
| #include "com/com_fileio.h" | ||||
| #include "com/datalink.h" | ||||
| 
 | ||||
| #include "utils/debounce.h" | ||||
| #include "utils/timebase.h" | ||||
| 
 | ||||
| #include "bus/event_queue.h" | ||||
| 
 | ||||
| // ---- Private prototypes --------
 | ||||
| 
 | ||||
| static void conf_gpio(void); | ||||
| static void conf_usart(void); | ||||
| static void conf_systick(void); | ||||
| static void conf_subsystems(void); | ||||
| static void conf_irq_prios(void); | ||||
| 
 | ||||
| // ---- Public functions ----------
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Initialize hardware resources | ||||
|  */ | ||||
| void hw_init(void) | ||||
| { | ||||
| 	conf_gpio(); | ||||
| 	conf_usart(); | ||||
| 	conf_systick(); | ||||
| 	conf_irq_prios(); | ||||
| 	conf_subsystems(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // ---- Private functions ---------
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| static void conf_irq_prios(void) | ||||
| { | ||||
| 	NVIC_SetPriorityGrouping(0); // 0 bits for sub-priority
 | ||||
| 
 | ||||
| 	// SysTick - highest prio, used for timeouts
 | ||||
| 	NVIC_SetPriority(SysTick_IRQn, 0); // SysTick - for timeouts
 | ||||
| 	NVIC_SetPriority(USART2_IRQn, 6); // USART - datalink
 | ||||
| 	NVIC_SetPriority(USART1_IRQn, 10); // USART - debug
 | ||||
| 
 | ||||
| 	// FIXME check , probably bad ports
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Configure SW subsystems | ||||
|  */ | ||||
| static void conf_subsystems(void) | ||||
| { | ||||
| 	// task scheduler subsystem
 | ||||
| 	timebase_init(15, 15); | ||||
| 
 | ||||
| 	// event and task queues
 | ||||
| 	queues_init(15, 15); | ||||
| 
 | ||||
| 	// initialize SBMP for ESP8266
 | ||||
| 	dlnk_init(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Configure GPIOs | ||||
|  */ | ||||
| static void conf_gpio(void) | ||||
| { | ||||
| 	GPIO_InitTypeDef gpio_cnf; | ||||
| 
 | ||||
| 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); | ||||
| 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); | ||||
| 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); | ||||
| 
 | ||||
| 	// Red LED
 | ||||
| 	gpio_cnf.GPIO_Pin = GPIO_Pin_13; | ||||
| 	gpio_cnf.GPIO_Mode = GPIO_Mode_Out_PP; | ||||
| 	gpio_cnf.GPIO_Speed = GPIO_Speed_10MHz; | ||||
| 	GPIO_Init(GPIOC, &gpio_cnf); | ||||
| 
 | ||||
| 	// colorled | sonar trig
 | ||||
| 	gpio_cnf.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13; | ||||
| 	gpio_cnf.GPIO_Mode = GPIO_Mode_Out_PP; | ||||
| 	gpio_cnf.GPIO_Speed = GPIO_Speed_10MHz; | ||||
| 	GPIO_Init(GPIOB, &gpio_cnf); | ||||
| 
 | ||||
| 	gpio_cnf.GPIO_Pin = GPIO_Pin_14; | ||||
| 	gpio_cnf.GPIO_Mode = GPIO_Mode_IPD; | ||||
| 	gpio_cnf.GPIO_Speed = GPIO_Speed_10MHz; | ||||
| 	GPIO_Init(GPIOB, &gpio_cnf); | ||||
| 
 | ||||
| 	// A0-sonar trig | UART2 - debug, UART1 - esp
 | ||||
| 	gpio_cnf.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_9 | GPIO_Pin_10; | ||||
| 	gpio_cnf.GPIO_Mode = GPIO_Mode_AF_PP; | ||||
| 	GPIO_Init(GPIOA, &gpio_cnf); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Configure USARTs | ||||
|  */ | ||||
| static void conf_usart(void) | ||||
| { | ||||
| 	// Debug interface, working as stdout/stderr.
 | ||||
| 	debug_iface = usart_iface_init(USART2, 115200, 256, 256); | ||||
| 	setvbuf(stdout, NULL, _IONBF, 0); | ||||
| 	setvbuf(stderr, NULL, _IONBF, 0); | ||||
| 	debug_iface->file = stdout; | ||||
| 
 | ||||
| 	// Datalink iface
 | ||||
| 	data_iface = usart_iface_init(USART1, 460800, 256, 256); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Configure 1 kHz SysTick w/ interrupt | ||||
|  */ | ||||
| static void conf_systick(void) | ||||
| { | ||||
| 	SysTick_Config(F_CPU / 1000); | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "main.h" | ||||
| 
 | ||||
| void hw_init(void); | ||||
| 
 | ||||
| @ -1,25 +1,69 @@ | ||||
| /* Includes ------------------------------------------------------------------*/ | ||||
| #include "stm32f10x.h" | ||||
| #include <stdio.h> | ||||
| #include "main.h" | ||||
| #include "hw_init.h" | ||||
| 
 | ||||
| #include "com/debug.h" | ||||
| #include "com/com_fileio.h" | ||||
| #include "com/com_iface.h" | ||||
| #include "bus/event_queue.h" | ||||
| #include "bus/event_handler.h" | ||||
| #include "utils/timebase.h" | ||||
| 
 | ||||
| #include "colorled.h" | ||||
| #include "display.h" | ||||
| #include <math.h> | ||||
| #include <sbmp.h> | ||||
| 
 | ||||
| void poll_subsystems(void) | ||||
| { | ||||
| 	// poll serial buffers (runs callback)
 | ||||
| 	com_poll(debug_iface); | ||||
| 	com_poll(data_iface); | ||||
| 
 | ||||
| 	// run queued tasks
 | ||||
| 	tq_poll(); | ||||
| 
 | ||||
| 	// handle queued events
 | ||||
| 	Event evt; | ||||
| 
 | ||||
| 	until_timeout(2) { // take 2 ms max
 | ||||
| 		if (eq_take(&evt)) { | ||||
| 			run_event_handler(&evt); | ||||
| 		} else { | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void blinky(void* arg) | ||||
| { | ||||
| 	(void)arg; | ||||
| 	GPIOC->ODR ^= 1<<13; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| int main(void) | ||||
| { | ||||
| 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); | ||||
| 	hw_init(); | ||||
| 	display_init(); | ||||
| 
 | ||||
| 	GPIO_InitTypeDef gpio_cnf; | ||||
| 	gpio_cnf.GPIO_Pin = GPIO_Pin_13; | ||||
| 	gpio_cnf.GPIO_Mode = GPIO_Mode_Out_PP; | ||||
| 	gpio_cnf.GPIO_Speed = GPIO_Speed_2MHz; | ||||
| 	banner("*** STM32F103K8T6 RGB LED demo ***"); | ||||
| 	banner_info("(c) Ondrej Hruska, 2016"); | ||||
| 	banner_info("Katedra mereni K338, CVUT FEL"); | ||||
| 
 | ||||
| 	GPIO_Init(GPIOC, &gpio_cnf); | ||||
| 
 | ||||
| 	add_periodic_task(blinky, NULL, 500, false); | ||||
| 
 | ||||
| 	while (1) { | ||||
| 		GPIOC->ODR ^= GPIO_Pin_13; | ||||
| 
 | ||||
| 		// delay
 | ||||
| 		for (int i = 0; i < 1000000; i++); | ||||
| 		poll_subsystems(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void dlnk_rx(SBMP_Datagram *dg) | ||||
| { | ||||
| 	dbg("Rx dg type %d", dg->type); | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,18 @@ | ||||
| #ifndef MAIN_H | ||||
| #define MAIN_H | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <inttypes.h> | ||||
| 
 | ||||
| #include <stm32f10x.h> | ||||
| 
 | ||||
| #define MIN(a,b) ((a)>(b)?(b):(a)) | ||||
| #define MAX(a,b) ((a)<(b)?(b):(a)) | ||||
| #define STR_HELPER(x) #x | ||||
| #define STR(x) STR_HELPER(x) | ||||
| 
 | ||||
| #endif // MAIN_H
 | ||||
| @ -0,0 +1,40 @@ | ||||
| #include "com/debug.h" | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <stdint.h> | ||||
| #include <inttypes.h> | ||||
| 
 | ||||
| static void reset_when_done(void) | ||||
| { | ||||
| 	for (uint32_t i = 0; i < 20000; i++) { | ||||
| 		if (com_tx_done(debug_iface)) break; | ||||
| 	} | ||||
| 
 | ||||
| 	NVIC_SystemReset(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void *malloc_safe_do(size_t size, const char* file, uint32_t line) | ||||
| { | ||||
| 	void *mem = malloc(size); | ||||
| 	if (mem == NULL) { | ||||
| 		// malloc failed
 | ||||
| 		error("Malloc failed in file %s on line %"PRIu32, file, line); | ||||
| 		reset_when_done(); | ||||
| 	} | ||||
| 
 | ||||
| 	return mem; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void *calloc_safe_do(size_t nmemb, size_t size, const char* file, uint32_t line) | ||||
| { | ||||
| 	void *mem = calloc(size, nmemb); | ||||
| 	if (mem == NULL) { | ||||
| 		// malloc failed
 | ||||
| 		error("Malloc failed in file %s on line %"PRIu32, file, line); | ||||
| 		reset_when_done(); | ||||
| 	} | ||||
| 
 | ||||
| 	return mem; | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| #ifndef MALLOC_SAFE_H | ||||
| #define MALLOC_SAFE_H | ||||
| 
 | ||||
| /**
 | ||||
|  * Malloc that prints error and restarts the system on failure. | ||||
|  */ | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| void *malloc_safe_do(size_t size, const char* file, uint32_t line); | ||||
| void *calloc_safe_do(size_t nmemb, size_t size, const char* file, uint32_t line); | ||||
| 
 | ||||
| #define malloc_s(size)        malloc_safe_do(size,        __FILE__, __LINE__) | ||||
| #define calloc_s(nmemb, size) calloc_safe_do(nmemb, size, __FILE__, __LINE__) | ||||
| 
 | ||||
| #endif // MALLOC_SAFE_H
 | ||||
| @ -0,0 +1,24 @@ | ||||
| #include "main.h" | ||||
| #include "utils/timebase.h" | ||||
| #include "com/debug.h" | ||||
| 
 | ||||
| #ifdef  USE_FULL_ASSERT | ||||
| 
 | ||||
| /**
 | ||||
|   * @brief  Reports the name of the source file and the source line number | ||||
|   *         where the assert_param error has occurred. | ||||
|   * @param  file: pointer to the source file name | ||||
|   * @param  line: assert_param error line source number | ||||
|   * @return None | ||||
|   */ | ||||
| void __attribute__((noreturn)) | ||||
| assert_failed(uint8_t* file, uint32_t line) | ||||
| { | ||||
| 	error("Assert failed in file %s, line %"PRIu32".", file, line); | ||||
| 
 | ||||
| 	/* Infinite loop */ | ||||
| 	while (1); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| @ -0,0 +1,32 @@ | ||||
| #include <errno.h> | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| register char * stack_ptr asm("sp"); | ||||
| 
 | ||||
| caddr_t _sbrk(int incr) | ||||
| { | ||||
| 	extern char end __asm("end"); | ||||
| 	static char *heap_end; | ||||
| 	char *prev_heap_end; | ||||
| 
 | ||||
| 	if (heap_end == 0) | ||||
| 		heap_end = &end; | ||||
| 
 | ||||
| 	prev_heap_end = heap_end; | ||||
| 	if (heap_end + incr > stack_ptr) | ||||
| 	{ | ||||
| //		write(1, "Heap and stack collision\n", 25);
 | ||||
| //		abort();
 | ||||
| 		errno = ENOMEM; | ||||
| 		return (caddr_t) -1; | ||||
| 	} | ||||
| 
 | ||||
| 	heap_end += incr; | ||||
| 
 | ||||
| 	return (caddr_t) prev_heap_end; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // Other systcalls are defined in
 | ||||
| // - com/com_fileio.c
 | ||||
| // - hw_utils/reset.h
 | ||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -0,0 +1,161 @@ | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| #include <malloc.h> | ||||
| 
 | ||||
| #include "circbuf.h" | ||||
| #include "malloc_safe.h" | ||||
| 
 | ||||
| // --- Circbuf data structure ----
 | ||||
| 
 | ||||
| /** Offset in void* buffer */ | ||||
| #define PV_OFFS(pvBuf, elem_size, index) ((uint8_t*)(pvBuf) + ((elem_size)*(index))) | ||||
| 
 | ||||
| 
 | ||||
| // Instance structure
 | ||||
| struct circbuf_struct { | ||||
| 	void *buf; | ||||
| 	size_t elem_size; | ||||
| 	size_t cap; | ||||
| 	size_t lr; // last read pos
 | ||||
| 	size_t nw; // next write pos
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Write data to a CircBuf slot | ||||
|  * @param cb     : circbuf | ||||
|  * @param index  : slot index | ||||
|  * @param source : data source | ||||
|  */ | ||||
| static void write_buffer(CircBuf *cb, size_t index, const void *source) | ||||
| { | ||||
| 	memcpy(PV_OFFS(cb->buf, cb->elem_size, index), source, cb->elem_size); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Copy data from a CircBuf slot to a buffer | ||||
|  * @param cb     : circbuf | ||||
|  * @param index  : slot index | ||||
|  * @param dest   : destination buffer | ||||
|  */ | ||||
| static void read_buffer(const CircBuf *cb, size_t index, void *dest) | ||||
| { | ||||
| 	memcpy(dest, PV_OFFS(cb->buf, cb->elem_size, index), cb->elem_size); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Create a cbuf */ | ||||
| CircBuf *cbuf_create(size_t capacity, size_t elem_size) | ||||
| { | ||||
| 	// add one, because one is always unused.
 | ||||
| 	capacity++; | ||||
| 
 | ||||
| 	// Allocate the structure
 | ||||
| 	CircBuf *cb = malloc_s(sizeof(CircBuf)); | ||||
| 
 | ||||
| 	// allocate the buffer
 | ||||
| 	cb->buf = malloc_s(capacity * elem_size); | ||||
| 
 | ||||
| 	// set capacity, clear state
 | ||||
| 	cb->elem_size = elem_size; | ||||
| 	cb->cap = capacity; | ||||
| 	cbuf_clear(cb); | ||||
| 
 | ||||
| 	return cb; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Release cbuf memory */ | ||||
| void cbuf_destroy(CircBuf *cb) | ||||
| { | ||||
| 	if (cb != NULL) { | ||||
| 		if (cb->buf != NULL) { | ||||
| 			free(cb->buf); | ||||
| 		} | ||||
| 
 | ||||
| 		free(cb); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Check if cbuf is full */ | ||||
| bool cbuf_full(const CircBuf *cb) | ||||
| { | ||||
| 	if (cb == NULL) return false; | ||||
| 
 | ||||
| 	return (cb->lr == cb->nw); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Check if cbuf is empty */ | ||||
| bool cbuf_empty(const CircBuf *cb) | ||||
| { | ||||
| 	if (cb == NULL) return true; | ||||
| 
 | ||||
| 	return ((cb->lr + 1) % cb->cap) == cb->nw; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Write a byte to the buffer, if space left */ | ||||
| bool cbuf_append(CircBuf *cb, const void *source) | ||||
| { | ||||
| 	if (cb == NULL) return false; | ||||
| 	if (source == NULL) return false; | ||||
| 	if (cbuf_full(cb)) return false; | ||||
| 
 | ||||
| 	write_buffer(cb, cb->nw, source); | ||||
| 
 | ||||
| 	// increment
 | ||||
| 	cb->nw++; | ||||
| 	if (cb->nw == cb->cap) cb->nw = 0; | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Push value to the end, like a stack. */ | ||||
| bool cbuf_push(CircBuf *cb, const void *source) | ||||
| { | ||||
| 	if (cb == NULL) return false; | ||||
| 	if (source == NULL) return false; | ||||
| 	if (cbuf_full(cb)) return false; | ||||
| 
 | ||||
| 	write_buffer(cb, cb->lr, source); | ||||
| 
 | ||||
| 	// move lr back
 | ||||
| 	if (cb->lr == 0) { | ||||
| 		cb->lr = cb->cap - 1; // wrap to the end
 | ||||
| 	} else { | ||||
| 		cb->lr--; | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Read one byte, if not empty. */ | ||||
| bool cbuf_pop(CircBuf *cb, void *dest) | ||||
| { | ||||
| 	if (cb == NULL || dest == NULL) return false; | ||||
| 	if (cbuf_empty(cb)) return false; | ||||
| 
 | ||||
| 	// increment
 | ||||
| 	cb->lr++; | ||||
| 	if (cb->lr == cb->cap) cb->lr = 0; | ||||
| 
 | ||||
| 	read_buffer(cb, cb->lr, dest); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Clear a cbuf */ | ||||
| void cbuf_clear(CircBuf *cb) | ||||
| { | ||||
| 	if (cb == NULL) return; | ||||
| 
 | ||||
| 	cb->lr = cb->cap - 1; | ||||
| 	cb->nw = 0; | ||||
| } | ||||
| @ -0,0 +1,93 @@ | ||||
| /**
 | ||||
|  * @file circbuf.h | ||||
|  * @author Ondřej Hruška, 2016 | ||||
|  * | ||||
|  * Circular buffer / queue / stack. | ||||
|  * Slots are pre-allocated, values are copied into the buffer. | ||||
|  * | ||||
|  * The buffer may be used as a stack, event queue or a simple buffer. | ||||
|  * | ||||
|  * ------------------------------------- | ||||
|  * | ||||
|  *         NW        LR | ||||
|  * append -> [][][][] -> pop | ||||
|  *                    <- push | ||||
|  * | ||||
|  * NW - next write pointer (stack base) | ||||
|  * LR - last read position (stack top) | ||||
|  * | ||||
|  * ------------------------------------- | ||||
|  * | ||||
|  * MIT license | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| 
 | ||||
| typedef struct circbuf_struct CircBuf; | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Initialize a circular buffer. The buffer is malloc'd. | ||||
|  * @param capacity  : buffer capacity | ||||
|  * @param elem_size : size of one element | ||||
|  * @return pointer to the buffer instance | ||||
|  */ | ||||
| CircBuf *cbuf_create(size_t capacity, size_t elem_size); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Destroy a buffer, freeing used memory. | ||||
|  * | ||||
|  * @attention | ||||
|  * If the buffer items have malloc'd members, you have | ||||
|  * to free them manually to avoid a memory leak. | ||||
|  * | ||||
|  * @param cb : buffer | ||||
|  */ | ||||
| void cbuf_destroy(CircBuf *cb); | ||||
| 
 | ||||
| 
 | ||||
| /** Test for full buffer */ | ||||
| bool cbuf_full(const CircBuf *cb); | ||||
| 
 | ||||
| 
 | ||||
| /** Test for empty buffer */ | ||||
| bool cbuf_empty(const CircBuf *cb); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Append a value to the buffer (FIFO) | ||||
|  * @param cb     : buffer | ||||
|  * @param source : pointer to a value (will be copied) | ||||
|  * @return success | ||||
|  */ | ||||
| bool cbuf_append(CircBuf *cb, const void *source); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Push a value into the circbuf (LIFO). | ||||
|  * | ||||
|  * @param cb     : buffer | ||||
|  * @param source : pointer to a value (will be copied) | ||||
|  * @return success | ||||
|  */ | ||||
| bool cbuf_push(CircBuf *cb, const void *source); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Read a value from the buffer, return susccess. | ||||
|  * | ||||
|  * @param cb   : buffer | ||||
|  * @param dest : read destionation. If NULL, value is discarded. | ||||
|  * @return success | ||||
|  */ | ||||
| bool cbuf_pop(CircBuf *cb, void *dest); | ||||
| 
 | ||||
| 
 | ||||
| /** @brief Remove all data from buffer */ | ||||
| void cbuf_clear(CircBuf *cb); | ||||
| @ -0,0 +1,171 @@ | ||||
| #include "main.h" | ||||
| #include "debounce.h" | ||||
| #include "timebase.h" | ||||
| #include "malloc_safe.h" | ||||
| 
 | ||||
| // ms debounce time
 | ||||
| 
 | ||||
| #define DEF_DEBO_TIME 20 | ||||
| 
 | ||||
| typedef struct { | ||||
| 	GPIO_TypeDef *GPIOx;         ///< GPIO base
 | ||||
| 	uint16_t pin;                ///< bit mask
 | ||||
| 	bool state;                  ///< current state
 | ||||
| 	bool invert;                 ///< invert pin
 | ||||
| 	debo_id_t id;                ///< pin ID
 | ||||
| 	ms_time_t debo_time;         ///< debouncing time (ms)
 | ||||
| 	ms_time_t counter_0;         ///< counter for falling edge (ms)
 | ||||
| 	ms_time_t counter_1;         ///< counter for rising edge (ms)
 | ||||
| 	void (*falling_cb)(void); | ||||
| 	void (*rising_cb)(void); | ||||
| } debo_slot_t; | ||||
| 
 | ||||
| 
 | ||||
| /** Number of allocated slots */ | ||||
| static size_t debo_slot_count = 0; | ||||
| 
 | ||||
| /** Slots array */ | ||||
| static debo_slot_t *debo_slots; | ||||
| 
 | ||||
| /** Next free pin ID for make_id() */ | ||||
| static debo_id_t next_pin_id = 1; | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Get a valid free pin ID for a new entry. | ||||
|  * @return the ID. | ||||
|  */ | ||||
| static debo_id_t make_id(void) | ||||
| { | ||||
| 	debo_id_t id = next_pin_id++; | ||||
| 
 | ||||
| 	// make sure no task is given PID 0
 | ||||
| 	if (next_pin_id == DEBO_PIN_NONE) { | ||||
| 		next_pin_id++; | ||||
| 	} | ||||
| 
 | ||||
| 	return id; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Init the debouncer */ | ||||
| void debounce_init(size_t slot_count) | ||||
| { | ||||
| 	debo_slots = calloc_s(slot_count, sizeof(debo_slot_t)); | ||||
| 	debo_slot_count = slot_count; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Register a pin */ | ||||
| debo_id_t debo_register_pin(debo_init_t *init) | ||||
| { | ||||
| 	assert_param(IS_GPIO_ALL_PERIPH(init->GPIOx)); | ||||
| 	assert_param(IS_GET_GPIO_PIN(init->pin)); | ||||
| 
 | ||||
| 	for (size_t i = 0; i < debo_slot_count; i++) { | ||||
| 		debo_slot_t *slot = &debo_slots[i]; | ||||
| 
 | ||||
| 		if (slot->id != DEBO_PIN_NONE) continue; // slot is used
 | ||||
| 
 | ||||
| 		slot->GPIOx = init->GPIOx; | ||||
| 		slot->pin = init->pin; | ||||
| 		slot->falling_cb = init->falling_cb; | ||||
| 		slot->rising_cb = init->rising_cb; | ||||
| 		slot->invert = init->invert; | ||||
| 		slot->counter_0 = 0; | ||||
| 		slot->counter_1 = 0; | ||||
| 		slot->debo_time = (init->debo_time == 0) ? DEF_DEBO_TIME : init->debo_time; | ||||
| 
 | ||||
| 		bool state = GPIO_ReadInputDataBit(slot->GPIOx, slot->pin); | ||||
| 		if (slot->invert) state = !state; | ||||
| 		slot->state = state; | ||||
| 
 | ||||
| 		slot->id = make_id(); | ||||
| 
 | ||||
| 		return slot->id; | ||||
| 	} | ||||
| 
 | ||||
| 	return DEBO_PIN_NONE; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Callback that must be called every 1 ms */ | ||||
| void debo_periodic_task(void) | ||||
| { | ||||
| 	for (size_t i = 0; i < debo_slot_count; i++) { | ||||
| 		debo_slot_t *slot = &debo_slots[i]; | ||||
| 		if (slot->id == DEBO_PIN_NONE) continue; // unused
 | ||||
| 
 | ||||
| 		bool state = GPIO_ReadInputDataBit(slot->GPIOx, slot->pin); | ||||
| 		if (slot->invert) state = !state; | ||||
| 
 | ||||
| 		if (slot->state != state) { | ||||
| 			if (state == 0) { | ||||
| 				// falling
 | ||||
| 
 | ||||
| 				if (slot->counter_0++ == slot->debo_time) { | ||||
| 					slot->state = 0; | ||||
| 
 | ||||
| 					if (slot->falling_cb != NULL) { | ||||
| 						slot->falling_cb(); | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| 				// rising
 | ||||
| 
 | ||||
| 				if (slot->counter_1++ == slot->debo_time) { | ||||
| 					slot->state = 1; | ||||
| 
 | ||||
| 					if (slot->rising_cb != NULL) { | ||||
| 						slot->rising_cb(); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			// reset counters
 | ||||
| 			slot->counter_0 = 0; | ||||
| 			slot->counter_1 = 0; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Check if a pin is high | ||||
|  * @param pin_id : Slot ID | ||||
|  * @return true if the pin is registered and is HIGH | ||||
|  */ | ||||
| bool debo_pin_state(debo_id_t pin_id) | ||||
| { | ||||
| 	if (pin_id == DEBO_PIN_NONE) return false; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < debo_slot_count; i++) { | ||||
| 		debo_slot_t *slot = &debo_slots[i]; | ||||
| 		if (slot->id != pin_id) continue; | ||||
| 
 | ||||
| 		return slot->state; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Remove a pin entry from the debouncer. | ||||
|  * @param pin_id : Slot ID | ||||
|  * @return true if task found & removed. | ||||
|  */ | ||||
| bool debo_remove_pin(debo_id_t pin_id) | ||||
| { | ||||
| 	if (pin_id == DEBO_PIN_NONE) return false; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < debo_slot_count; i++) { | ||||
| 		debo_slot_t *slot = &debo_slots[i]; | ||||
| 		if (slot->id != pin_id) continue; | ||||
| 
 | ||||
| 		slot->id = DEBO_PIN_NONE; | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| @ -0,0 +1,62 @@ | ||||
| #pragma once | ||||
| #include "main.h" | ||||
| #include "utils/timebase.h" | ||||
| 
 | ||||
| // Debouncer requires that you setup SysTick first.
 | ||||
| 
 | ||||
| /** Debounced pin ID - used for state readout */ | ||||
| typedef uint32_t debo_id_t; | ||||
| 
 | ||||
| /** debo_id_t indicating unused slot */ | ||||
| #define DEBO_PIN_NONE 0 | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Initialize the debouncer. | ||||
|  * | ||||
|  * You have to also register the periodic task to timebase. | ||||
|  * | ||||
|  * @param pin_count : number of pin slots to allocate | ||||
|  */ | ||||
| void debounce_init(size_t pin_count); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief 1 ms periodic callback for debouncer. Must be registered to timebase. | ||||
|  */ | ||||
| void debo_periodic_task(void); | ||||
| 
 | ||||
| 
 | ||||
| typedef struct { | ||||
| 	GPIO_TypeDef *GPIOx;		///< GPIO base
 | ||||
| 	uint16_t pin;				///< pin mask
 | ||||
| 	ms_time_t debo_time;		///< debounce time in ms, 0 = default (20 ms)
 | ||||
| 	bool invert;				///< invert value read from GPIO (button to ground)
 | ||||
| 	void (*rising_cb)(void);	///< callback when the pin goes HIGH
 | ||||
| 	void (*falling_cb)(void);	///< callback when the pin goes LOW
 | ||||
| } debo_init_t; | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Add a pin for debouncing. | ||||
|  * | ||||
|  * The pin state will be checked with the configured hysteresis | ||||
|  * and callbacks will be called when a state change is detected. | ||||
|  */ | ||||
| debo_id_t debo_register_pin(debo_init_t *init_struct); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Check if a pin is high | ||||
|  * @param pin_id : Slot ID | ||||
|  * @return true if the pin is registered and is HIGH | ||||
|  */ | ||||
| bool debo_pin_state(debo_id_t pin_id); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Remove a pin entry from the debouncer. | ||||
|  * @param pin_id : Slot ID | ||||
|  * @return true if task found & removed. | ||||
|  */ | ||||
| bool debo_remove_pin(debo_id_t pin_id); | ||||
| @ -0,0 +1,33 @@ | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #include "matcher.h" | ||||
| 
 | ||||
| void matcher_reset(matcher_t *m) | ||||
| { | ||||
| 	m->cursor = 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Handle incoming char. Returns true if this char completed the match. */ | ||||
| bool matcher_test(matcher_t * m, uint8_t b) | ||||
| { | ||||
| 	// If mismatch, rewind (and check at 0)
 | ||||
| 	if (m->pattern[m->cursor] != b) { | ||||
| 		m->cursor = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	// Check for match
 | ||||
| 	if (m->pattern[m->cursor] == b) { | ||||
| 		// Good char
 | ||||
| 		m->cursor++; | ||||
| 		if (m->pattern[m->cursor] == 0) { // end of pattern
 | ||||
| 			m->cursor = 0; // rewind
 | ||||
| 			return true; // indicate success
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| /**
 | ||||
|  * @file matcher.h | ||||
|  * @author Ondřej Hruška, 2016 | ||||
|  * | ||||
|  * String matching utility. | ||||
|  * 
 | ||||
|  * Matcher can be used for detecting a pattern in a stream of characters. | ||||
|  * With each incoming character, call matcher_test(). 
 | ||||
|  * 
 | ||||
|  * It will return true if the character completed a match. | ||||
|  * 
 | ||||
|  * MIT license | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| typedef struct { | ||||
| 	const char *pattern; | ||||
| 	size_t cursor; | ||||
| } matcher_t; | ||||
| 
 | ||||
| 
 | ||||
| /** reset match progress */ | ||||
| void matcher_reset(matcher_t *m); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Consume an incoming character. | ||||
|  * If this char was the last char of the pattern, returns true and resets matcher. | ||||
|  * | ||||
|  * If the char is not in the pattern, resets matcher. | ||||
|  * | ||||
|  * @returns true if the char concluded the expected pattern. | ||||
|  */ | ||||
| bool matcher_test(matcher_t * mb, uint8_t b); | ||||
| @ -0,0 +1,70 @@ | ||||
| #include <stdint.h> | ||||
| #include <malloc.h> | ||||
| 
 | ||||
| #include "meanbuf.h" | ||||
| #include "malloc_safe.h" | ||||
| 
 | ||||
| 
 | ||||
| struct meanbuf_struct { | ||||
| 	float * buf; // buffer (allocated at init)
 | ||||
| 	size_t cap; // capacity
 | ||||
| 	size_t nw; // next write index
 | ||||
| 	float mean; // updated on write
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /** Init a buffer */ | ||||
| MeanBuf *meanbuf_create(size_t size) | ||||
| { | ||||
| 	MeanBuf *mb = malloc_s(sizeof(MeanBuf)); | ||||
| 
 | ||||
| 	if (size < 1) size = 1; | ||||
| 
 | ||||
| 	mb->buf = calloc_s(size, sizeof(float)); // calloc, so it starts with zeros.
 | ||||
| 	mb->cap = size; | ||||
| 	mb->nw = 0; | ||||
| 	mb->mean = 0; | ||||
| 
 | ||||
| 	// clean buffer
 | ||||
| 	for (uint16_t i = 0; i < size; i++) { | ||||
| 		mb->buf[i] = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	return mb; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void meanbuf_destroy(MeanBuf *mb) | ||||
| { | ||||
| 	if (mb == NULL) return; | ||||
| 
 | ||||
| 	if (mb->buf != NULL) { | ||||
| 		free(mb->buf); | ||||
| 	} | ||||
| 
 | ||||
| 	free(mb); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Add a value to the buffer. Returns current mean. */ | ||||
| float meanbuf_add(MeanBuf *mb, float f) | ||||
| { | ||||
| 	// add sample
 | ||||
| 	mb->buf[mb->nw++] = f; | ||||
| 	if (mb->nw == mb->cap) mb->nw = 0; | ||||
| 
 | ||||
| 	// calculate average
 | ||||
| 	float acc = 0; | ||||
| 	for (size_t i = 0; i < mb->cap; i++) { | ||||
| 		acc += mb->buf[i]; | ||||
| 	} | ||||
| 
 | ||||
| 	acc /= mb->cap; | ||||
| 
 | ||||
| 	return mb->mean = acc; | ||||
| } | ||||
| 
 | ||||
| float meanbuf_current(MeanBuf *mb) | ||||
| { | ||||
| 	return mb->mean; | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| /**
 | ||||
|  * @file meanbuf.h | ||||
|  * @author Ondřej Hruška, 2016 | ||||
|  * | ||||
|  * Averaging float buffer. (You can adjust it to use doubles, if you prefer.) | ||||
|  * | ||||
|  * The meanbuf_create() function allocates a buffer. | ||||
|  * | ||||
|  * You can then call meanbuf_add() to add a new value into the buffer (and remove the oldest). | ||||
|  * This function returns the current average value. | ||||
|  * | ||||
|  * This buffer can be used for signal smoothing (such as from an analogue sensor). | ||||
|  * | ||||
|  * MIT license | ||||
|  */ | ||||
| 
 | ||||
| 
 | ||||
| #pragma once | ||||
| #include <stdlib.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| typedef struct meanbuf_struct MeanBuf; | ||||
| 
 | ||||
| /** Init a buffer */ | ||||
| MeanBuf *meanbuf_create(size_t size); | ||||
| 
 | ||||
| /** Deinit a buffer (free buffer array) */ | ||||
| void meanbuf_destroy(MeanBuf *mb); | ||||
| 
 | ||||
| /** Add a value to the buffer. Returns current mean. */ | ||||
| float meanbuf_add(MeanBuf *mb, float f); | ||||
| 
 | ||||
| float meanbuf_current(MeanBuf *bm); | ||||
| @ -0,0 +1,4 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #define MAX(a,b) ((a) > (b) ? (a) : (b)) | ||||
| #define MIN(a,b) ((a) < (b) ? (a) : (b)) | ||||
| @ -0,0 +1,286 @@ | ||||
| #include "str_utils.h" | ||||
| #include "matcher.h" | ||||
| #include "malloc_safe.h" | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| // Lot of this stuff is actually not needed anymore,
 | ||||
| // it was written for the ESP AT firmware, which is no longer used.
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Escape a char. | ||||
|  * @returns what to put after backslash, or '\0' for no escape. | ||||
|  */ | ||||
| static char escape_char(char c) | ||||
| { | ||||
| 	switch (c) { | ||||
| 		case '\r': return 'r'; | ||||
| 		case '\n': return 'n'; | ||||
| 		case '\t': return 't'; | ||||
| 		case '\\': return '\\'; | ||||
| 		default: return 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Escape string in place | ||||
|  */ | ||||
| void str_escape_ip(char * buf, size_t buf_len) | ||||
| { | ||||
| 	size_t i = 0; | ||||
| 
 | ||||
| 	// string length (updated when escapes are performed)
 | ||||
| 	size_t slen = strlen(buf); | ||||
| 
 | ||||
| 	for (; i < buf_len - 1 && buf[i] != 0; i++) { | ||||
| 		char replace = escape_char(buf[i]); | ||||
| 
 | ||||
| 		// Escape, shift trailing chars
 | ||||
| 		if (replace != 0) { | ||||
| 			if (i >= buf_len - 2) { | ||||
| 				break; // discard the char, escape wouldn't fit.
 | ||||
| 			} | ||||
| 
 | ||||
| 			// (could be faster if moved starting at the end)
 | ||||
| 
 | ||||
| 			char m = buf[i + 1]; // remember next char
 | ||||
| 
 | ||||
| 			buf[i] = '\\'; | ||||
| 			buf[i + 1] = replace; | ||||
| 
 | ||||
| 			slen++; // account for the added backslash
 | ||||
| 
 | ||||
| 			// shift trailing chars
 | ||||
| 			for (size_t j = i + 2; j <= slen; j++) { | ||||
| 				char n = buf[j]; | ||||
| 				buf[j] = m; | ||||
| 				m = n; | ||||
| 			} | ||||
| 
 | ||||
| 			i++; // skip the insterted slash
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	buf[i] = 0; // add terminator (in case end of string was reached)
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void str_escape(char *dest, const char *src, size_t dest_len) | ||||
| { | ||||
| 	size_t di = 0, si = 0; | ||||
| 
 | ||||
| 	for (; src[si] != 0 && di < dest_len - 1; si++) { | ||||
| 		char orig = src[si]; | ||||
| 		char replace = escape_char(orig); | ||||
| 
 | ||||
| 		if (replace == 0) { | ||||
| 			dest[di++] = orig; | ||||
| 		} else { | ||||
| 			if (di >= dest_len - 2) { | ||||
| 				break; // out of space
 | ||||
| 			} | ||||
| 
 | ||||
| 			dest[di++] = '\\'; | ||||
| 			dest[di++] = replace; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	dest[di] = 0; // append terminator
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| int32_t strpos(const char *haystack, const char *needle) | ||||
| { | ||||
| 	const char *p = strstr(haystack, needle); | ||||
| 	if (p) return (p - haystack); | ||||
| 	return -1;   // Not found = -1.
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| int32_t strpos_upto(const char *haystack, const char *needle, size_t limit) | ||||
| { | ||||
| 	if (limit <= 0) return strpos(haystack, needle); | ||||
| 
 | ||||
| 	matcher_t m = {needle, 0}; | ||||
| 	char c; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < limit; i++, haystack++) { | ||||
| 		c = *haystack; | ||||
| 		if (c == 0) break; | ||||
| 
 | ||||
| 		if (matcher_test(&m, (uint8_t)c)) { | ||||
| 			return i - strlen(needle) + 1; // match occured on the last needle char
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return -1; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| int32_t strpos_upto_match(const char *haystack, const char *needle, const char *endmatch) | ||||
| { | ||||
| 	if (endmatch == NULL) return strpos(haystack, needle); | ||||
| 
 | ||||
| 	matcher_t matcher_needle = {needle, 0}; | ||||
| 	matcher_t matcher_end = {endmatch, 0}; | ||||
| 	char c; | ||||
| 
 | ||||
| 	for (int i = 0;; i++, haystack++) { | ||||
| 		c = *haystack; | ||||
| 		if (c == 0) break; | ||||
| 
 | ||||
| 		// match
 | ||||
| 		if (matcher_test(&matcher_needle, (uint8_t)c)) { | ||||
| 			return i - strlen(needle) + 1; // match occured on the last needle char
 | ||||
| 		} | ||||
| 
 | ||||
| 		// end
 | ||||
| 		if (matcher_test(&matcher_end, (uint8_t)c)) { | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return -1; | ||||
| } | ||||
| 
 | ||||
| size_t str_copy(char * dest, const char *src) | ||||
| { | ||||
| 	char c; | ||||
| 	size_t i = 0; | ||||
| 	while ((c = *src++) != 0) { | ||||
| 		*dest++ = c; | ||||
| 		i++; | ||||
| 	} | ||||
| 	return i; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Decode URL-encoded string in place. | ||||
|  */ | ||||
| void urldecode_ip(char *str) | ||||
| { | ||||
| 	unsigned int x; | ||||
| 
 | ||||
| 	for (size_t i = 0; str[i] != 0; i++) { | ||||
| 		char c = str[i]; | ||||
| 		if (c == '+') { | ||||
| 			str[i] = ' '; | ||||
| 		} else if (c == '%') { | ||||
| 			// decode the byte
 | ||||
| 			sscanf(&str[i + 1], "%02x", &x); | ||||
| 			str[i] = (char)x; | ||||
| 
 | ||||
| 			// shift following chars
 | ||||
| 			for (size_t a = i + 3, b = i + 1;; a++, b++) { | ||||
| 				str[b] = str[a]; // move
 | ||||
| 				if (str[a] == 0) break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * url-decode string, put output in a buffer. | ||||
|  */ | ||||
| void urldecode(char *dest, const char *src) | ||||
| { | ||||
| 	unsigned int x; | ||||
| 	size_t si = 0, di = 0; | ||||
| 
 | ||||
| 	for (; src[si] != 0; si++) { | ||||
| 		char c = src[si]; | ||||
| 		if (c == '+') { | ||||
| 			dest[di++] = ' '; | ||||
| 		} else if (c == '%') { | ||||
| 			// decode the byte
 | ||||
| 			sscanf(&src[si + 1], "%02x", &x); | ||||
| 			dest[di++] = (char)x; | ||||
| 
 | ||||
| 			si += 2; | ||||
| 		} else { | ||||
| 			dest[di++] = c; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// add terminator
 | ||||
| 	dest[di] = 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * url-decode string, put output in a buffer. | ||||
|  * Limit operation to N chars in input string | ||||
|  */ | ||||
| void urldecode_n(char *dest, const char *src, size_t count) | ||||
| { | ||||
| 	unsigned int x; | ||||
| 	size_t si = 0, di = 0; | ||||
| 
 | ||||
| 	for (; src[si] != 0 && si < count; si++) { | ||||
| 		char c = src[si]; | ||||
| 		if (c == '+') { | ||||
| 			dest[di++] = ' '; | ||||
| 		} else if (c == '%') { | ||||
| 			// decode the byte
 | ||||
| 			sscanf(&src[si + 1], "%02x", &x); | ||||
| 			dest[di++] = (char)x; | ||||
| 
 | ||||
| 			si += 2; | ||||
| 		} else { | ||||
| 			dest[di++] = c; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// add terminator
 | ||||
| 	dest[di] = 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool get_query_value(char *buffer, const char *querystring, const char *key, size_t buf_len) | ||||
| { | ||||
| 	bool retval; | ||||
| 
 | ||||
| 	size_t qs_len = strlen(querystring); | ||||
| 
 | ||||
| 	char *ptrn = malloc_s(strlen(key) + 3); // &key=\0
 | ||||
| 	sprintf(ptrn, "&%s=", key); | ||||
| 	matcher_t m = {ptrn, 1}; // pretend ampersand was already matched
 | ||||
| 
 | ||||
| 	for (size_t i = 0; i < qs_len; i++) { | ||||
| 		char c = querystring[i]; | ||||
| 		if (matcher_test(&m, (uint8_t)c)) { | ||||
| 			// found the match
 | ||||
| 			i++; // advance past the equals sign
 | ||||
| 
 | ||||
| 			size_t seg_end = i; | ||||
| 			while (seg_end < qs_len && querystring[seg_end] != '&') { | ||||
| 				seg_end++; | ||||
| 			} | ||||
| 
 | ||||
| 			if (seg_end - i > buf_len) seg_end = i + buf_len; | ||||
| 
 | ||||
| 			if (seg_end == i) { | ||||
| 				buffer[0] = 0; // strncpy behaves strange with length 0
 | ||||
| 			} else { | ||||
| 				urldecode_n(buffer, querystring + i, seg_end - i); | ||||
| 			} | ||||
| 
 | ||||
| 			retval = true; | ||||
| 			goto done; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// not found
 | ||||
| 	retval = false; | ||||
| done: | ||||
| 	free(ptrn); | ||||
| 	return retval; | ||||
| } | ||||
| @ -0,0 +1,75 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #define streq(a, b) (strcmp((a), (b)) == 0) | ||||
| #define streqi(a, b) (strcasecmp((a), (b)) == 0) | ||||
| 
 | ||||
| /**
 | ||||
|  * Escape string, storing result in a buffer. | ||||
|  */ | ||||
| void str_escape(char *dest, const char *src, size_t dest_len); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Escape special chars in a string, IN PLACE. | ||||
|  * | ||||
|  * If string is too long after escaping, last chars are dropped. | ||||
|  * | ||||
|  * @param buf the buffer, containing 0-terminated string. | ||||
|  * @param buflen buffer length | ||||
|  */ | ||||
| void str_escape_ip(char *buf, size_t buf_len); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Get position of needle in a haystack. | ||||
|  * -1 if not found. | ||||
|  */ | ||||
| int32_t strpos(const char *haystack, const char *needle); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Find substring position, ending at index 'limit'. | ||||
|  * Limit <= 0 means no limit. | ||||
|  * Returns index of the first character of needle in haystack. | ||||
|  */ | ||||
| int32_t strpos_upto(const char *haystack, const char *needle, size_t limit); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Find substring position, ending when endmatch is encountered. (Substring within endmatch *can* be reported). | ||||
|  * Returns index of the first character of needle in haystack. | ||||
|  */ | ||||
| int32_t strpos_upto_match(const char *haystack, const char *needle, const char *endmatch); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Like sprintf, except without formatting | ||||
|  */ | ||||
| size_t str_copy(char * dest, const char *src); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Decode url-encoded string, store result in dest. | ||||
|  */ | ||||
| void urldecode(char *dest, const char *src); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Decode url-encoded string in place. | ||||
|  */ | ||||
| void urldecode_ip(char *str); | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Retrieve & url-decode a query string value by name. | ||||
|  * | ||||
|  * @param buffer - target buffer | ||||
|  * @param querystring - string (foo=bar&baz=moi) | ||||
|  * @param key - key to retrieve | ||||
|  * @param buf_len - length of the target buffer | ||||
|  * @return true if found. | ||||
|  */ | ||||
| bool get_query_value(char *buffer, const char *querystring, const char *key, size_t buf_len); | ||||
| @ -0,0 +1,331 @@ | ||||
| #include "timebase.h" | ||||
| #include "bus/event_queue.h" | ||||
| #include "com/debug.h" | ||||
| #include "malloc_safe.h" | ||||
| 
 | ||||
| // Time base
 | ||||
| static volatile ms_time_t SystemTime_ms = 0; | ||||
| 
 | ||||
| 
 | ||||
| typedef struct { | ||||
| 	/** User callback with arg */ | ||||
| 	void (*callback)(void *); | ||||
| 	/** Arg for the arg callback */ | ||||
| 	void *cb_arg; | ||||
| 	/** Callback interval */ | ||||
| 	ms_time_t interval_ms; | ||||
| 	/** Counter, when reaches interval_ms, is cleared and callback is called. */ | ||||
| 	ms_time_t countup; | ||||
| 	/** Unique task ID (for cancelling / modification) */ | ||||
| 	task_pid_t pid; | ||||
| 	/** Enable flag - disabled tasks still count, but CB is not run */ | ||||
| 	bool enabled; | ||||
| 	/** Marks that the task is due to be run */ | ||||
| 	bool enqueue; | ||||
| } periodic_task_t; | ||||
| 
 | ||||
| 
 | ||||
| typedef struct { | ||||
| 	/** User callback with arg */ | ||||
| 	void (*callback)(void *); | ||||
| 	/** Arg for the arg callback */ | ||||
| 	void *cb_arg; | ||||
| 	/** Counter, when reaches 0ms, callback is called and the task is removed */ | ||||
| 	ms_time_t countdown_ms; | ||||
| 	/** Unique task ID (for cancelling / modification) */ | ||||
| 	task_pid_t pid; | ||||
| 	/** Whether this task is long and needs posting on the queue */ | ||||
| 	bool enqueue; | ||||
| } future_task_t; | ||||
| 
 | ||||
| 
 | ||||
| static size_t periodic_slot_count = 0; | ||||
| static size_t future_slot_count = 0; | ||||
| 
 | ||||
| static periodic_task_t *periodic_tasks; | ||||
| static future_task_t *future_tasks; | ||||
| 
 | ||||
| 
 | ||||
| /** Init timebase */ | ||||
| void timebase_init(size_t periodic, size_t future) | ||||
| { | ||||
| 	periodic_slot_count = periodic; | ||||
| 	future_slot_count = future; | ||||
| 
 | ||||
| 	periodic_tasks = calloc_s(periodic, sizeof(periodic_task_t)); | ||||
| 	future_tasks = calloc_s(future, sizeof(future_task_t)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static task_pid_t next_task_pid = 1; // 0 (PID_NONE) is reserved
 | ||||
| 
 | ||||
| 
 | ||||
| /** Get a valid free PID for a new task. */ | ||||
| static task_pid_t make_pid(void) | ||||
| { | ||||
| 	task_pid_t pid = next_task_pid++; | ||||
| 
 | ||||
| 	// make sure no task is given PID 0
 | ||||
| 	if (next_task_pid == PID_NONE) { | ||||
| 		next_task_pid++; | ||||
| 	} | ||||
| 
 | ||||
| 	return pid; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Take an empty periodic task slot and populate the basics. */ | ||||
| static periodic_task_t* claim_periodic_task_slot(ms_time_t interval, bool enqueue) | ||||
| { | ||||
| 	for (size_t i = 0; i < periodic_slot_count; i++) { | ||||
| 		periodic_task_t *task = &periodic_tasks[i]; | ||||
| 		if (task->pid != PID_NONE) continue; // task is used
 | ||||
| 
 | ||||
| 		task->countup = 0; | ||||
| 		task->interval_ms = interval - 1; | ||||
| 		task->enqueue = enqueue; | ||||
| 		task->pid = make_pid(); | ||||
| 		task->enabled = true; | ||||
| 		return task; | ||||
| 	} | ||||
| 
 | ||||
| 	error("Periodic task table full."); | ||||
| 
 | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Take an empty future task slot and populate the basics. */ | ||||
| static future_task_t* claim_future_task_slot(ms_time_t delay, bool enqueue) | ||||
| { | ||||
| 	for (size_t i = 0; i < future_slot_count; i++) { | ||||
| 		future_task_t *task = &future_tasks[i]; | ||||
| 		if (task->pid != PID_NONE) continue; // task is used
 | ||||
| 
 | ||||
| 		task->countdown_ms = delay; | ||||
| 		task->enqueue = enqueue; | ||||
| 		task->pid = make_pid(); | ||||
| 		return task; | ||||
| 	} | ||||
| 
 | ||||
| 	error("Future task table full."); | ||||
| 
 | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /** Add a periodic task with an arg. */ | ||||
| task_pid_t add_periodic_task(void (*callback)(void*), void* arg, ms_time_t interval, bool enqueue) | ||||
| { | ||||
| 	periodic_task_t *task = claim_periodic_task_slot(interval, enqueue); | ||||
| 
 | ||||
| 	if (task == NULL) return PID_NONE; | ||||
| 
 | ||||
| 	task->callback = callback; | ||||
| 	task->cb_arg = arg; | ||||
| 
 | ||||
| 	return task->pid; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Schedule a future task, with uint32_t argument. */ | ||||
| task_pid_t schedule_task(void (*callback)(void*), void *arg, ms_time_t delay, bool enqueue) | ||||
| { | ||||
| 	future_task_t *task = claim_future_task_slot(delay, enqueue); | ||||
| 
 | ||||
| 	if (task == NULL) return PID_NONE; | ||||
| 
 | ||||
| 	task->callback = callback; | ||||
| 	task->cb_arg = arg; | ||||
| 
 | ||||
| 	return task->pid; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Enable or disable a periodic task. */ | ||||
| bool enable_periodic_task(task_pid_t pid, FunctionalState enable) | ||||
| { | ||||
| 	if (pid == PID_NONE) return false; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < periodic_slot_count; i++) { | ||||
| 		periodic_task_t *task = &periodic_tasks[i]; | ||||
| 		if (task->pid != pid) continue; | ||||
| 
 | ||||
| 		task->enabled = (enable == ENABLE); | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Check if a periodic task is enabled */ | ||||
| bool is_periodic_task_enabled(task_pid_t pid) | ||||
| { | ||||
| 	if (pid == PID_NONE) return false; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < periodic_slot_count; i++) { | ||||
| 		periodic_task_t *task = &periodic_tasks[i]; | ||||
| 		if (task->pid != pid) continue; | ||||
| 
 | ||||
| 		return task->enabled; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| bool reset_periodic_task(task_pid_t pid) | ||||
| { | ||||
| 	if (pid == PID_NONE) return false; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < periodic_slot_count; i++) { | ||||
| 		periodic_task_t *task = &periodic_tasks[i]; | ||||
| 		if (task->pid != pid) continue; | ||||
| 
 | ||||
| 		task->countup = 0; | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Remove a periodic task. */ | ||||
| bool remove_periodic_task(task_pid_t pid) | ||||
| { | ||||
| 	if (pid == PID_NONE) return false; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < periodic_slot_count; i++) { | ||||
| 		periodic_task_t *task = &periodic_tasks[i]; | ||||
| 		if (task->pid != pid) continue; | ||||
| 
 | ||||
| 		task->pid = PID_NONE; // mark unused
 | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Abort a scheduled task. */ | ||||
| bool abort_scheduled_task(task_pid_t pid) | ||||
| { | ||||
| 	if (pid == PID_NONE) return false; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < future_slot_count; i++) { | ||||
| 		future_task_t *task = &future_tasks[i]; | ||||
| 		if (task->pid != pid) continue; | ||||
| 
 | ||||
| 		task->pid = PID_NONE; // mark unused
 | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Run a periodic task */ | ||||
| static void run_periodic_task(periodic_task_t *task) | ||||
| { | ||||
| 	if (!task->enabled) return; | ||||
| 
 | ||||
| 	if (task->enqueue) { | ||||
| 		// queued task
 | ||||
| 		tq_post(task->callback, task->cb_arg); | ||||
| 	} else { | ||||
| 		// immediate task
 | ||||
| 		task->callback(task->cb_arg); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Run a future task */ | ||||
| static void run_future_task(future_task_t *task) | ||||
| { | ||||
| 	if (task->enqueue) { | ||||
| 		// queued task
 | ||||
| 		tq_post(task->callback, task->cb_arg); | ||||
| 	} else { | ||||
| 		// immediate task
 | ||||
| 		task->callback(task->cb_arg); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Millisecond callback, should be run in the SysTick handler. | ||||
|  */ | ||||
| void timebase_ms_cb(void) | ||||
| { | ||||
| 	// increment global time
 | ||||
| 	SystemTime_ms++; | ||||
| 
 | ||||
| 	// run periodic tasks
 | ||||
| 	for (size_t i = 0; i < periodic_slot_count; i++) { | ||||
| 		periodic_task_t *task = &periodic_tasks[i]; | ||||
| 		if (task->pid == PID_NONE) continue; // unused
 | ||||
| 
 | ||||
| 		if (task->countup++ >= task->interval_ms) { | ||||
| 			// run if enabled
 | ||||
| 			run_periodic_task(task); | ||||
| 			// restart counter
 | ||||
| 			task->countup = 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// run planned future tasks
 | ||||
| 	for (size_t i = 0; i < future_slot_count; i++) { | ||||
| 		future_task_t *task = &future_tasks[i]; | ||||
| 		if (task->pid == PID_NONE) continue; // unused
 | ||||
| 
 | ||||
| 		if (task->countdown_ms-- == 0) { | ||||
| 			// run
 | ||||
| 			run_future_task(task); | ||||
| 			// release the slot
 | ||||
| 			task->pid = PID_NONE; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /** Seconds delay */ | ||||
| void delay_s(uint32_t s) | ||||
| { | ||||
| 	while (s-- != 0) { | ||||
| 		delay_ms(1000); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Delay N ms */ | ||||
| void delay_ms(ms_time_t ms) | ||||
| { | ||||
| 	ms_time_t start = SystemTime_ms; | ||||
| 	while ((SystemTime_ms - start) < ms); // overrun solved by unsigned arithmetic
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Get milliseconds elapsed since start timestamp */ | ||||
| ms_time_t ms_elapsed(ms_time_t start) | ||||
| { | ||||
| 	return SystemTime_ms - start; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Get current timestamp. */ | ||||
| ms_time_t ms_now(void) | ||||
| { | ||||
| 	return SystemTime_ms; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** Helper for looping with periodic branches */ | ||||
| bool ms_loop_elapsed(ms_time_t *start, ms_time_t duration) | ||||
| { | ||||
| 	if (SystemTime_ms - *start >= duration) { | ||||
| 		*start = SystemTime_ms; | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| @ -0,0 +1,159 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| /**
 | ||||
|  * To use the Timebase functionality, | ||||
|  * set up SysTick to 1 kHz and call | ||||
|  * timebase_ms_cb() in the IRQ. | ||||
|  * | ||||
|  * If you plan to use pendable future tasks, | ||||
|  * also make sure you call run_pending_tasks() | ||||
|  * in your main loop. | ||||
|  * | ||||
|  * This is not needed for non-pendable tasks. | ||||
|  */ | ||||
| 
 | ||||
| #include "main.h" | ||||
| 
 | ||||
| 
 | ||||
| /** Task PID. */ | ||||
| typedef uint32_t task_pid_t; | ||||
| 
 | ||||
| /** Time value in ms */ | ||||
| typedef uint32_t ms_time_t; | ||||
| 
 | ||||
| // PID value that can be used to indicate no task
 | ||||
| #define PID_NONE 0 | ||||
| 
 | ||||
| /** Loop until timeout - use in place of while() or for(). break and continue work too! */ | ||||
| #define until_timeout(to_ms) for(uint32_t _utmeo = ms_now(); ms_elapsed(_utmeo) < (to_ms);) | ||||
| 
 | ||||
| /** Retry a call until a timeout. Variable 'suc' is set to the return value. Must be defined. */ | ||||
| #define retry_TO(to_ms, call)  \ | ||||
| 	until_timeout(to_ms) { \
 | ||||
| 		suc = call; \
 | ||||
| 		if (suc) break; \
 | ||||
| 	} | ||||
| 
 | ||||
| /** Init timebase, allocate slots for tasks. */ | ||||
| void timebase_init(size_t periodic_count, size_t future_count); | ||||
| 
 | ||||
| /** Must be called every 1 ms */ | ||||
| void timebase_ms_cb(void); | ||||
| 
 | ||||
| 
 | ||||
| // --- Periodic -----------------------------------------------
 | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Add a periodic task with an arg. | ||||
|  * @param callback : task callback | ||||
|  * @param arg      : callback argument | ||||
|  * @param interval : task interval (ms) | ||||
|  * @param enqueue  : put on the task queue when due | ||||
|  * @return task PID | ||||
|  */ | ||||
| task_pid_t add_periodic_task(void (*callback)(void *), void *arg, ms_time_t interval, bool enqueue); | ||||
| 
 | ||||
| 
 | ||||
| /** Destroy a periodic task. */ | ||||
| bool remove_periodic_task(task_pid_t pid); | ||||
| 
 | ||||
| /** Enable or disable a periodic task. Returns true on success. */ | ||||
| bool enable_periodic_task(task_pid_t pid, FunctionalState cmd); | ||||
| 
 | ||||
| /** Check if a periodic task exists and is enabled. */ | ||||
| bool is_periodic_task_enabled(task_pid_t pid); | ||||
| 
 | ||||
| /** Reset timer for a task */ | ||||
| bool reset_periodic_task(task_pid_t pid); | ||||
| 
 | ||||
| 
 | ||||
| // --- Future -------------------------------------------------
 | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Schedule a future task, with uint32_t argument. | ||||
|  * @param callback : task callback | ||||
|  * @param arg      : callback argument | ||||
|  * @param delay    : task delay (ms) | ||||
|  * @param enqueue  : put on the task queue when due | ||||
|  * @return task PID | ||||
|  */ | ||||
| task_pid_t schedule_task(void (*callback_arg)(void *), void *arg, ms_time_t delay, bool enqueue); | ||||
| 
 | ||||
| 
 | ||||
| /** Abort a scheduled task. */ | ||||
| bool abort_scheduled_task(task_pid_t pid); | ||||
| 
 | ||||
| 
 | ||||
| // --- Waiting functions --------------------------------------
 | ||||
| 
 | ||||
| /** Get milliseconds elapsed since start timestamp */ | ||||
| ms_time_t ms_elapsed(ms_time_t start); | ||||
| 
 | ||||
| 
 | ||||
| /** Get current timestamp. */ | ||||
| ms_time_t ms_now(void); | ||||
| 
 | ||||
| 
 | ||||
| /** Delay using SysTick */ | ||||
| void delay_ms(ms_time_t ms); | ||||
| 
 | ||||
| 
 | ||||
| /** Delay N seconds */ | ||||
| void delay_s(uint32_t s); | ||||
| 
 | ||||
| 
 | ||||
| inline __attribute__((always_inline)) | ||||
| void delay_cycles(uint32_t n) | ||||
| { | ||||
| 	uint32_t l = n >> 2; | ||||
| 
 | ||||
| 	__asm volatile( | ||||
| 		"0: mov r0,r0;" | ||||
| 		"subs %[count], #1;" | ||||
| 		"bne 0b;" | ||||
| 		: [count] "+r"(l) | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| inline __attribute__((always_inline)) | ||||
| void delay_ns(uint32_t ns) | ||||
| { | ||||
| 	delay_cycles(ns / 24); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Microsecond delay. | ||||
|  * @param us | ||||
|  */ | ||||
| inline __attribute__((always_inline)) | ||||
| void delay_us(uint32_t us) | ||||
| { | ||||
| 	delay_ns(us * 1150); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Check if time since `start` elapsed. | ||||
|  * | ||||
|  * If so, sets the *start variable to the current time. | ||||
|  * | ||||
|  * Example: | ||||
|  * | ||||
|  *   ms_time_t s = ms_now(); | ||||
|  * | ||||
|  *   while(1) { | ||||
|  *     if (ms_loop_elapsed(&s, 100)) { | ||||
|  *       // this is called every 100 ms
 | ||||
|  *     } | ||||
|  *     // ... rest of the loop ...
 | ||||
|  *   } | ||||
|  * | ||||
|  * @param start     start time variable | ||||
|  * @param duration  delay length | ||||
|  * @return delay elapsed; start was updated. | ||||
|  */ | ||||
| bool ms_loop_elapsed(ms_time_t *start, ms_time_t duration); | ||||
					Loading…
					
					
				
		Reference in new issue