Initial import
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
#include <common.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
|
||||
uint32_t cb_payload; ///< payload passed to the callbac
|
||||
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 (*callback)(uint32_t, bool);
|
||||
} 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;
|
||||
|
||||
void debo_periodic_task(void *unused);
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
add_periodic_task(debo_periodic_task, NULL, 1, false);
|
||||
}
|
||||
|
||||
|
||||
/** Register a pin */
|
||||
debo_id_t debo_register_pin(debo_init_t *init)
|
||||
{
|
||||
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->callback = init->callback;
|
||||
slot->cb_payload = init->cb_payload;
|
||||
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 = HAL_GPIO_ReadPin(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 *unused)
|
||||
{
|
||||
UNUSED(unused);
|
||||
|
||||
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 = HAL_GPIO_ReadPin(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->callback != NULL) {
|
||||
slot->callback(slot->cb_payload, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// rising
|
||||
|
||||
if (slot->counter_1++ == slot->debo_time) {
|
||||
slot->state = 1;
|
||||
|
||||
if (slot->callback != NULL) {
|
||||
slot->callback(slot->cb_payload, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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,60 @@
|
||||
#ifndef MPORK_DEBOUNCE_H
|
||||
#define MPORK_DEBOUNCE_H
|
||||
|
||||
#include <common.h>
|
||||
#include "timebase.h"
|
||||
|
||||
// Debouncer requires that you set up timebase 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.
|
||||
*
|
||||
* Registers the callback.
|
||||
*
|
||||
* @param pin_count : number of pin slots to allocate
|
||||
*/
|
||||
void debounce_init(size_t pin_count);
|
||||
|
||||
|
||||
typedef struct {
|
||||
GPIO_TypeDef *GPIOx; ///< GPIO base
|
||||
uint16_t pin; ///< pin mask
|
||||
bool invert; ///< invert value read from GPIO (button to ground)
|
||||
ms_time_t debo_time; ///< debounce time in ms, 0 = default (20 ms)
|
||||
uint32_t cb_payload; ///< Value passed to the callback func
|
||||
void (*callback)(uint32_t, bool); ///< callback
|
||||
} 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);
|
||||
|
||||
#endif /* MPORK_DEBOUNCE_H */
|
||||
@@ -0,0 +1,119 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <inttypes.h>
|
||||
#include "debug.h"
|
||||
#include "timebase.h"
|
||||
|
||||
|
||||
void dbg_printf(const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
vprintf(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;
|
||||
|
||||
printf("%4"PRIu32".%03"PRIu32" ", secs, ms);
|
||||
|
||||
dbg_raw(tag);
|
||||
|
||||
vprintf(fmt, va);
|
||||
dbg_raw(DEBUG_EOL);
|
||||
}
|
||||
|
||||
/** 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, ...)
|
||||
{
|
||||
v100_attr(FMT_WHITE);
|
||||
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
dbg_va_base(fmt, DEBUG_TAG_INFO, va);
|
||||
va_end(va);
|
||||
|
||||
v100_attr(FMT_RESET);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Print a log message with an INFO tag and newline */
|
||||
void banner(const char *fmt, ...)
|
||||
{
|
||||
v100_attr(FMT_GREEN, FMT_BRIGHT);
|
||||
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
dbg_va_base(fmt, DEBUG_TAG_INFO, va);
|
||||
va_end(va);
|
||||
|
||||
v100_attr(FMT_RESET);
|
||||
}
|
||||
|
||||
|
||||
/** Print a log message with a warning tag and newline */
|
||||
void warn(const char *fmt, ...)
|
||||
{
|
||||
v100_attr(FMT_YELLOW, FMT_BRIGHT);
|
||||
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
dbg_va_base(fmt, DEBUG_TAG_WARN, va);
|
||||
va_end(va);
|
||||
|
||||
v100_attr(FMT_RESET);
|
||||
}
|
||||
|
||||
|
||||
/** Print a log message with an ERROR tag and newline */
|
||||
void error(const char *fmt, ...)
|
||||
{
|
||||
v100_attr(FMT_RED, FMT_BRIGHT);
|
||||
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
dbg_va_base(fmt, DEBUG_TAG_ERROR, va);
|
||||
va_end(va);
|
||||
|
||||
v100_attr(FMT_RESET);
|
||||
}
|
||||
|
||||
|
||||
void v100_attr_(uint8_t count, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, count);
|
||||
|
||||
putchar(27);
|
||||
putchar('[');
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int attr = va_arg(va, int);
|
||||
|
||||
// comma
|
||||
if (i > 0) putchar(';');
|
||||
|
||||
// number
|
||||
printf("%d", attr);
|
||||
}
|
||||
|
||||
putchar('m');
|
||||
|
||||
va_end(va);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef MPORK_DEBUG_H
|
||||
#define MPORK_DEBUG_H
|
||||
|
||||
#include <common.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
// helper to mark printf functions
|
||||
#define PRINTF_LIKE __attribute__((format(printf, 1, 2)))
|
||||
|
||||
// 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)
|
||||
{
|
||||
fputs(str, stdout);
|
||||
}
|
||||
|
||||
|
||||
/** Print a char to the debug interface */
|
||||
static inline void dbg_raw_c(char c)
|
||||
{
|
||||
putchar((uint8_t) c);
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
/** 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;
|
||||
|
||||
|
||||
/** 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 v100_attr(...) v100_attr_(VA_NUM_ARGS(__VA_ARGS__), __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Send formatting code to a com interface
|
||||
*/
|
||||
void v100_attr_(uint8_t count, ...);
|
||||
|
||||
#endif //MPORK_DEBUG_H
|
||||
@@ -0,0 +1,37 @@
|
||||
#include <common.h>
|
||||
|
||||
#include "handlers.h"
|
||||
#include "malloc_safe.h"
|
||||
|
||||
static void reset_when_done(void)
|
||||
{
|
||||
//
|
||||
|
||||
HAL_NVIC_SystemReset();
|
||||
}
|
||||
|
||||
|
||||
void *malloc_safe_do(size_t size, const char* file, uint32_t line)
|
||||
{
|
||||
void *mem = malloc(size);
|
||||
if (mem == NULL) {
|
||||
// malloc failed
|
||||
user_error_file_line("Malloc failed", 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(nmemb, size);
|
||||
if (mem == NULL) {
|
||||
// malloc failed
|
||||
user_error_file_line("Calloc failed", file, line);
|
||||
reset_when_done();
|
||||
}
|
||||
|
||||
return mem;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef MPORK_MALLOC_SAFE_H
|
||||
#define MPORK_MALLOC_SAFE_H
|
||||
|
||||
/**
|
||||
* Malloc that prints error and restarts the system on failure.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <stm32f1xx_hal.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 //MPORK_MALLOC_SAFE_H
|
||||
@@ -0,0 +1,83 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <usart.h>
|
||||
#include <sys/stat.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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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 1: // stdout
|
||||
case 2: // stderr
|
||||
HAL_UART_Transmit(&huart1, (uint8_t*)buf, len, 10);
|
||||
return len;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// region stubs
|
||||
|
||||
int _read(int fd, char *buf, int len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _fstat(int file, struct stat *st)
|
||||
{
|
||||
st->st_mode = S_IFCHR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _lseek(int file, int ptr, int dir)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _close(int file)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int _isatty(int fd)
|
||||
{
|
||||
if (fd == 0 || fd == 1 || fd == 2)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// endregion
|
||||
@@ -0,0 +1,349 @@
|
||||
#include <common.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "timebase.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, bool 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;
|
||||
}
|
||||
|
||||
|
||||
bool set_periodic_task_interval(task_pid_t pid, ms_time_t interval)
|
||||
{
|
||||
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->interval_ms = interval;
|
||||
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
|
||||
// FIXME re-implement queue
|
||||
//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
|
||||
// FIXME re-implement queue
|
||||
//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,165 @@
|
||||
#ifndef MPORK_TIMEBASE_H
|
||||
#define MPORK_TIMEBASE_H
|
||||
|
||||
/**
|
||||
* 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 <common.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, bool 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);
|
||||
|
||||
/** Set inteval */
|
||||
bool set_periodic_task_interval(task_pid_t pid, ms_time_t interval);
|
||||
|
||||
|
||||
// --- 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);
|
||||
|
||||
#endif //MPORK_TIMEBASE_H
|
||||
Reference in New Issue
Block a user