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