blinkenlights working w sonar

This commit is contained in:
2016-05-12 01:32:35 +02:00
parent 11764cbd7a
commit d01e40e174
48 changed files with 4627 additions and 952 deletions
+161
View File
@@ -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;
}
+93
View File
@@ -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);
+171
View File
@@ -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;
}
+62
View File
@@ -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);
+33
View File
@@ -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;
}
+39
View File
@@ -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);
+70
View File
@@ -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;
}
+33
View File
@@ -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);
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
+286
View File
@@ -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;
}
+75
View File
@@ -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);
+331
View File
@@ -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;
}
+159
View File
@@ -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);