Module

Actor

This is an implementation of Actor Model which is a conceptual concurrent computation model. Check out what Actor Model is here in wikipedia.

All actors with the same priority share one execution context. So every actor should be implemented in the way of run-to-completion. This implementation decision is maid for the resource-constrained microcontrollers. You can run actors in another separated execution context as giving it a different priority.

Integration Guide

  • ACTOR_DEFAULT_MESSAGE_SIZE

    • Memory is allocated with fixed-size blocks defined by ACTOR_DEFAULT_MESSAGE_SIZE to avoid external fragmentation.

  • ACTOR_PRIORITY_MAX

    • Threads are created according to the number of priorities. The default is 1.

  • ACTOR_PRIORITY_BASE

    • Priority increases or decreases by 1 based on it. The default is 1.

    • If the lower number the higher priority, then define ACTOR_PRIORITY_DESCENDING. The default is ascending.

Example

struct actor_msg {
    int id;
};

static uint8_t mem[256];
static uint8_t timer_mem[256];
static struct actor my_actor1;

static void my_actor1_handler(struct actor *actor, struct actor_msg *msg) {
    printf("actor called %p with msg id: %d", actor, msg->id);
    actor_free(msg);
}

int main(void) {
    actor_init(mem, sizeof(mem), 4096);
    actor_timer_init(timer_mem, sizeof(timer_mem));

    actor_set(&my_actor1, my_actor1_handler, 0);

    ...

    while (1) {
        struct actor_msg *msg = actor_alloc(sizeof(*msg));
        msg->id = 1;
        actor_send(&my_actor1, msg);

        sleep_ms(1000);
    }
}

Functions

int actor_init(void *mem, size_t memsize, size_t stack_size_bytes)
int actor_deinit(void)
struct actor *actor_new(actor_handler_t handler, int priority)
struct actor *actor_set(struct actor *actor, actor_handler_t handler, int priority)
int actor_send(struct actor *actor, struct actor_msg *msg)

Send a message to an actor.

Parameters:
  • actor – actor who gets the message

  • msg – message to send

Returns:

0 on success otherwise error

int actor_send_defer(struct actor *actor, struct actor_msg *msg, uint32_t millisec_delay)
struct actor_msg *actor_alloc(size_t payload_size)

Allocate a memory block of requested size + header.

Parameters:

payload_size – size of data to be used by application

Returns:

pointer of the memory block on success, NULL otherwise

int actor_free(struct actor_msg *msg)
size_t actor_cap(void)
size_t actor_len(void)

Typedefs

typedef void (*actor_handler_t)(struct actor *self, struct actor_msg *msg)
struct actor
#include <actor.h>

Public Members

struct list link
struct list messages
actor_handler_t handler
int priority

Defines

ACTOR_PRIORITY_MAX 1
ACTOR_PRIORITY_BASE 1

Functions

int actor_timer_init(void *mem, size_t memsize)
struct actor_timer *actor_timer_new(struct actor *actor, struct actor_msg *msg, uint32_t millisec_delay)
int actor_timer_delete(struct actor_timer *timer)
int actor_timer_start(struct actor_timer *timer)
int actor_timer_stop(struct actor_timer *timer)
int actor_timer_step(uint32_t elapsed_ms)
size_t actor_timer_cap(void)
size_t actor_timer_len(void)

Functions

void actor_lock(void)
void actor_unlock(void)
void actor_timer_boot(void)

Initialize a platform specific timer for actors.

Note

This will be called at the end of actor_timer_init()

void actor_pre_dispatch_hook(const struct actor *actor, const struct actor_msg *msg)
void actor_post_dispatch_hook(const struct actor *actor, const struct actor_msg *msg)

apptimer

Overview

It implements hierarchical timing wheels. Insertion and deletion is worst case O(1). Per-tick bookkeeping is also O(1), but is a little tricky because every time wheel unit time passing, all the slots of lower wheels get updated, which is not cache friendly.

Adjusting the number of wheels and slots, you might meet the requirements. e.g. it would be simple timing wheels when NR_WHEELS=1 with timeout limitation. There is space-time tradeoff. The more slots the faster while the more slots the more memory.

A timer takes 25 bytes on 32-bit system. 4 bytes more can be saved replacing doubly linked list with singly linked list. There would be no performance penalty to use singly linked list instead as long as slots are big enough for timers to be well distributed.

Integration Guide

Functions

void apptimer_init(void (*update_alarm)(apptimer_timeout_t timeout))

Initialize apptimer.

Parameters:

update_alarm – typically sets hardware timer counter to get notified at the timeout expiration.

apptimer_error_t apptimer_deinit(void)
apptimer_t apptimer_create(bool repeat, apptimer_callback_t callback)
apptimer_t apptimer_create_static(apptimer_t timer, bool repeat, apptimer_callback_t callback)
apptimer_error_t apptimer_destroy(apptimer_t timer)
apptimer_error_t apptimer_start(apptimer_t timer, apptimer_timeout_t timeout, void *callback_context)
apptimer_error_t apptimer_stop(apptimer_t timer)
int apptimer_count(void)
void apptimer_schedule(apptimer_timeout_t time_elapsed)

Process expirations and bookkeepings.

It should not be called from the interrupt context as expiry processing gets done here.

Typedefs

typedef apptimer_static_t *apptimer_t
typedef void (*apptimer_callback_t)(void *context)
typedef uintptr_t apptimer_timeout_t
union apptimer_static_t
#include <apptimer.h>

Public Members

char _size[28]
long _align

Enums

enum apptimer_error_t

Values:

enumerator APPTIMER_SUCCESS = 0
enumerator APPTIMER_ERROR = -1
enumerator APPTIMER_INVALID_PARAM = -2
enumerator APPTIMER_ALREADY_STARTED = -3
enumerator APPTIMER_TIME_LIMIT_EXCEEDED = -4

Defines

APPTIMER_MAX_TIMEOUT ((1UL << (sizeof(apptimer_timeout_t) * CHAR_BIT - 1)) - 1)
APPTIMER_DEBUG(...)

button

Overview

The debouncer that I implemented here learned from Elliot Williams’s Debounce Your Noisy Buttons article.

Integration Guide

Define Parameters

  • BUTTON_MAX

    • The maximum number of buttons. The default is 1.

  • BUTTON_SAMPLING_PERIOD_MS

    • The sampling period. The default is 10 milliseconds.

  • BUTTON_MIN_PRESS_TIME_MS

    • The default is 60 milliseconds.

  • BUTTON_REPEAT_DELAY_MS

    • The repeat handler is called after the defined delay while button holding. The default is 300 milliseconds.

  • BUTTON_REPEAT_RATE_MS

    • The repeat handler is called every BUTTON_REPEAT_RATE_MS while button holding. The default is 200 milliseconds.

  • BUTTON_CLICK_WINDOW_MS

    • The click handler is called with the number of clicks when another click comes in the time window. The default is 500 milliseconds.

Initialize GPIO to be used for button

This is platform specific, something like in case of NRF5:

int testbtn_get_state(void) {
    return (int)nrf_gpio_pin_read(USER_BUTTON);
}
void testbtn_hw_init(void) {
    nrf_gpio_cfg_input(USER_BUTTON, NRF_GPIO_PIN_PULLUP);
}

Interrupt can be a trigger to scan button states rather than polling all the time wasting cpu resource.

Initialize the module

A time function to calculate elapsed time should be provided when initializing: button_init(get_time_ms). The prototype is unsigned int get_time_ms(void).

As an example:

unsigned int get_time_ms(void) {
    return xTaskGetTickCount() * 1000 / configTICK_RATE_HZ;
}

Register buttons

static void on_button_event(enum button_event event,
        const struct button_data *info, void *ctx) {
    switch (event) {
    case BUTTON_EVT_CLICK:
        debug("%d click(s)", info->click);
        break;
    case BUTTON_EVT_PRESSED:
        debug("pressed at %lu", info->time_pressed);
        break;
    case BUTTON_EVT_RELEASED:
        debug("released at %lu", info->time_released);
        break;
    case BUTTON_EVT_HOLDING:
        debug("holding at %lu", info->time_repeat);
        break;
    }
}

void register_buttons(void) {
    button_register(testbtn_get_state, on_button_event, 0);
}

And scan

button_step();

then registered handler will be called when button activity detected.

Functions

void button_init(unsigned long (*get_time_ms)(void))
const struct button *button_register(int (*get_button_state)(void *ctx), button_handler_t handler, void *ctx)

Register a button

Parameters:
  • get_button_state[in] a function to get the button state

  • handlers[in] struct button_handlers

  • ctx[in] user context

Returns:

a handle if registered successfully. NULL otherwise

void button_update_handler(void *btn, button_handler_t handler, void *ctx)

Replace the button handler

Parameters:
Returns:

a handle if registered successfully. NULL otherwise

button_rc_t button_step(void)

Scan all buttons and update the states

@warn The millisecond time wraparound may add latency by BUTTON_SAMPLIING_PERIOD_MS.

Note

On 32-bit systems the millisecond time cycles approximately every 50 days. In case of the wraparound, clicks may resulted in false-positive if not button_step() called periodic until BUTTON_NO_ACTIVITY returned.

Parameters:

context[in] user context

Returns:

button_rc_t

Typedefs

typedef enum button_event button_event_t
typedef void (*button_handler_t)(button_event_t event, const struct button *button, void *ctx)
struct button
#include <button.h>

Public Members

unsigned int history
unsigned long time_pressed
unsigned long time_released
unsigned long time_repeat
uint8_t click

the number of clicks

Enums

enum button_rc_t

Values:

enumerator BUTTON_BUSY

Too many calls in a sampling period

enumerator BUTTON_SCANNING

Activity detected on buttons. Scanning for state

enumerator BUTTON_NO_ACTIVITY

No activity detected on buttons

enum button_event

Values:

enumerator BUTTON_EVT_PRESSED
enumerator BUTTON_EVT_RELEASED
enumerator BUTTON_EVT_HOLDING
enumerator BUTTON_EVT_CLICK

Defines

BUTTON_MAX 1
BUTTON_SAMPLING_PERIOD_MS 10U
BUTTON_MIN_PRESS_TIME_MS 60U
BUTTON_REPEAT_DELAY_MS 300U
BUTTON_REPEAT_RATE_MS 200U
BUTTON_CLICK_WINDOW_MS 500U

Functions

void button_lock(void)
void button_unlock(void)

CLI

Overview

Integration Guide

static size_t cli_read(void *buf, size_t bufsize)
{
    return fread(buf, bufsize, 1, stdin);
}

static size_t cli_write(void const *data, size_t datasize)
{
    return fwrite(data, datasize, 1, stdout);
}

static const cli_io_t io = {
    .read = cli_read,
    .write = cli_write,
};

static char cli_buffer[CLI_CMD_MAXLEN * 1/* the number of history to keep*/];
struct cli cli;

DEFINE_CLI_CMD_LIST(cli_commands, test_cmd, your_cmd);
cli_init(&cli, &io, cli_buffer, sizeof(cli_buffer));
cli_register_cmdlist(&cli, cli_commands);
#if never_return_unless_exit_command_received
cli_run(&cli);
#else
while (1) {
    cli_step(&cli);
}
#endif

Adding new command

DEFINE_CLI_CMD(your_cmd, "Description for your command") {
    ...
    return CLI_CMD_SUCCESS;
}

DEFINE_CLI_CMD(test_cmd, "Description for test command") {
    ...
    return CLI_CMD_SUCCESS;
}

Increasing the command buffer size

It should count the null character.

-DCLI_CMD_MAXLEN=128

Increasing the maximum command arguments

-DCLI_CMD_ARGS_MAXLEN=8

Customizing the messages

  • -DCLI_PROMPT=""

  • -DCLI_PROMPT_OK=""

  • -DCLI_PROMPT_ERROR=""

  • -DCLI_PROMPT_NOT_FOUND=""

  • -DCLI_PROMPT_START_MESSAGE=""

  • -DCLI_PROMPT_EXIT_MESSAGE=""

Functions

void cli_init(struct cli *cli, struct cli_io const *io, void *buf, size_t bufsize)
void cli_register_cmdlist(struct cli *cli, const struct cli_cmd **cmdlist)
void cli_run(struct cli *cli)
void cli_step(struct cli *cli)
struct cli_io
#include <cli.h>

Public Members

int (*read)(void *buf, size_t bufsize)
int (*write)(void const *data, size_t datasize)
struct cli
#include <cli.h>

Public Members

struct cli_io const *io
struct cli_cmd const **cmdlist
uint16_t cursor_pos
uint16_t history_next
uint16_t history_active
char previous_input
char *buf
size_t bufsize

Defines

CLI_ASSERT(exp)

Typedefs

typedef cli_cmd_error_t (*cli_cmd_func_t)(int argc, const char *argv[], const void *env)
struct cli_cmd
#include <cli_cmd.h>

Public Members

char const *name
cli_cmd_func_t func
char const *desc

Enums

enum cli_cmd_error_t

Values:

enumerator CLI_CMD_SUCCESS = 0
enumerator CLI_CMD_EXIT
enumerator CLI_CMD_NOT_FOUND
enumerator CLI_CMD_INVALID_PARAM
enumerator CLI_CMD_BLANK
enumerator CLI_CMD_ERROR

Defines

CLI_CMD_MAXLEN 62
CLI_CMD_ARGS_MAXLEN 8

Functions

const struct cli_io *cli_io_create(void)

Common

Functions

void libmcu_assertion_failed(const uintptr_t *pc, const uintptr_t *lr)

Defines

base64_encode libmcu_base64_encode
base64_decode libmcu_base64_decode
base64_decode_overwrite libmcu_base64_decode_overwrite

Functions

size_t base64_encode(char *buf, const void *data, size_t datasize)
size_t base64_decode(void *buf, const char *str, size_t strsize)
size_t base64_decode_overwrite(char *inout, size_t input_size)

Functions

static inline bool is_power2(unsigned int x)
int flsl(long x)

Enums

enum board_reboot_reason_t

Values:

enumerator BOARD_REBOOT_UNKNOWN
enumerator BOARD_REBOOT_POWER
enumerator BOARD_REBOOT_PIN
enumerator BOARD_REBOOT_SOFT
enumerator BOARD_REBOOT_PANIC
enumerator BOARD_REBOOT_WDT
enumerator BOARD_REBOOT_WDT_INT
enumerator BOARD_REBOOT_WDT_TASK
enumerator BOARD_REBOOT_DEEPSLEEP
enumerator BOARD_REBOOT_BROWNOUT
enumerator BOARD_REBOOT_SDIO

Functions

void board_init(void)
void board_reboot(void)
int board_reset_factory(void)
const char *board_get_version_string(void)
const char *board_get_build_date_string(void)
const char *board_get_serial_number_string(void)
board_reboot_reason_t board_get_reboot_reason(void)
const char *board_get_reboot_reason_string(board_reboot_reason_t reason)
unsigned long board_get_time_since_boot_ms(void)
long board_random(void)
uint8_t board_cpuload(int core_id)

Get overall CPU usage.

Parameters:

core_id – the core’s identifier

Returns:

percentage of 0 to 100

unsigned long board_get_free_heap_bytes(void)
unsigned long board_get_total_heap_bytes(void)
unsigned long board_get_heap_watermark(void)
unsigned long board_get_current_stack_watermark(void)
void *board_get_current_thread(void)

Functions

size_t cobs_encode(uint8_t *buf, size_t bufsize, void const *data, size_t datasize)
size_t cobs_decode(uint8_t *buf, size_t bufsize, uint8_t const *data, size_t datasize)
size_t cobs_decode_overwrite(uint8_t *inout, size_t maxlen)

Functions

uint32_t hash_murmur_32(const char *key)
uint32_t hash_dbj2_32(const char *key)

Functions

size_t hexdump(void *buf, size_t bufsize, void const *data, size_t datasize)
size_t hexdump_verbose(void *buf, size_t bufsize, void const *data, size_t datasize)
size_t hexdump_compute_output_size(size_t datasize)

Functions

size_t ringbuf_write(struct ringbuf *handle, const void *data, size_t datasize)
size_t ringbuf_write_cancel(struct ringbuf *handle, size_t size)
size_t ringbuf_peek(const struct ringbuf *handle, size_t offset, void *buf, size_t bufsize)
bool ringbuf_consume(struct ringbuf *handle, size_t consume_size)
size_t ringbuf_read(struct ringbuf *handle, size_t offset, void *buf, size_t bufsize)
size_t ringbuf_length(const struct ringbuf *handle)
size_t ringbuf_capacity(const struct ringbuf *handle)
bool ringbuf_create_static(struct ringbuf *handle, void *buf, size_t bufsize)
struct ringbuf *ringbuf_create(size_t space_size)
void ringbuf_destroy(struct ringbuf *handle)

Typedefs

typedef int (*syscall_writer_t)(const void *data, size_t data_len)
typedef int (*syscall_reader_t)(void *buf, size_t bufsize)

Functions

int _close(int file)
int _lseek(int file, int ptr, int dir)
int _fstat(int file, struct stat *st)
int _isatty(int file)
int _write(int file, char *ptr, int len)
int _read(int file, char *ptr, int len)
int _getpid(void)
void _kill(int pid, int sig)
void syscall_register_writer(syscall_writer_t writer)
void syscall_register_reader(syscall_reader_t reader)

Functions

void timeout_set(unsigned long *goal, unsigned long msec)
bool timeout_is_expired(unsigned long goal)
void sleep_ms(unsigned long msec)

Logging

Overview

On ARM Cortex-M cores, it uses 56 bytes stack and 181 bytes static memory at most with no dynamic allocation at all. And a log size is 17 bytes excluding a user custom message.

An example for a backend implementation can be found examples/memory_storage.c and a simple server-side script tools/scripts/translate_log.py.

Integration Guide

  • LOGGING_MESSAGE_MAXLEN : The default is 80 bytes

  • LOGGING_TAGS_MAXNUM : The default is 8

    • The unregistered tags over LOGGING_TAGS_MAXNUM share the global tag information

  • LOGGING_TAG : The default is __FILE__

    • The shorter __FILE__ the more code size preserved when you use the default

  • get_program_counter()

  • LOGGING_MAX_BACKENDS : The default is 1

Custom TAG instead of __FILE__

Please refer to test case and makefile.

Syncronization

Implement logging_lock_init(), logging_lock() and logging_unlock() in case of multi threaded environment.

Example

static uint8_t logbuf[512];

logging_init();
logging_add_backend(memory_storage_init(logbuf, sizeof(logbuf)));
logging_set_level_global(LOGGING_TYPE_DEBUG);
logging_set_level(LOGGING_TYPE_DEBUG);

debug("message");
info("RSSI %d", rssi);
error("i2c timeout");

Functions

void logging_init(logging_time_func_t time_func)
int logging_add_backend(const struct logging_backend *backend)
int logging_remove_backend(const struct logging_backend *backend)
size_t logging_write_with_backend(logging_t type, const struct logging_backend *backend, const struct logging_context *ctx, ...)
size_t logging_write(logging_t type, const struct logging_context *ctx, ...)
size_t logging_read(const struct logging_backend *backend, void *buf, size_t bufsize)
size_t logging_peek(const struct logging_backend *backend, void *buf, size_t bufsize)
size_t logging_consume(const struct logging_backend *backend, size_t consume_size)
size_t logging_count(const struct logging_backend *backend)
size_t logging_stringify(char *buf, size_t bufsize, const void *log)
void logging_set_level_tag(const char *tag, logging_t min_log_level)

Change the minimum log level to be saved for the tag.

A log gets saved only when the global log level is lower than the tag log level.

Parameters:
  • tag – module tag

  • min_log_level – one of logging_t

logging_t logging_get_level_tag(const char *tag)
void logging_set_level_global(logging_t min_log_level)
logging_t logging_get_level_global(void)
size_t logging_count_tags(void)
void logging_iterate_tag(void (*callback_each)(const char *tag, logging_t min_log_level))

Typedefs

typedef uint8_t logging_t
typedef unsigned long (*logging_time_func_t)(void)
struct logging_context
#include <logging.h>

Public Members

const char *tag
const void *pc
const void *lr

Enums

enum logging_type

Values:

enumerator LOGGING_TYPE_DEBUG = 0
enumerator LOGGING_TYPE_INFO
enumerator LOGGING_TYPE_WARN
enumerator LOGGING_TYPE_ERROR
enumerator LOGGING_TYPE_NONE
enumerator LOGGING_TYPE_MAX
struct logging_backend
#include <logging_backend.h>

Public Members

size_t (*write)(const void *data, size_t size)
size_t (*peek)(void *buf, size_t bufsize)
size_t (*read)(void *buf, size_t bufsize)

Read up to bufsize bytes from the storage

Return:

The number of bytes read is returned on success, 0 is returned on error or when no log is there.

size_t (*consume)(size_t size)

Remove the oldest log in the storage

Return:

The number of bytes removed.

size_t (*count)(void)

Defines

LOGGING_MAX_BACKENDS 1

Functions

void logging_lock_init(void)
void logging_lock(void)
void logging_unlock(void)

Metrics

Integration Guide

  1. Define your metrics with the METRICS_DEFINE macro in a file. e.g. metrics.def

  2. Let the compiler know where your file is located using METRICS_USER_DEFINES macro[1]. e.g. `-DMETRICS_USER_DEFINES="src/my_metrics.def"`` <https://github.com/onkwon/libmcu/blob/master/projects/runner.mk#L10>`_

  3. Set metric values with APIs. e.g. metrics_set(BatteryPCT, val)

  4. Set a timer to aggregate and send/save metrics periodically

location with METRICS_USER_DEFINES when you use the default file name and the file is in the include path.

Encoding

You can implement your own encoder using metrics_encode_header() and metrics_encode_each(). No encoder by default, meaning just a simple byte stream.

Syncronization

Implement metrics_lock() and metrics_unlock() in case of multi threaded environment.

static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void metircs_lock(void) {
    pthread_mutex_lock(&lock);
}
void metircs_unlock(void) {
    pthread_mutex_unlock(&lock);
}

Functions

void metrics_set(metric_key_t key, int32_t val)
int32_t metrics_get(metric_key_t key)
void metrics_increase(metric_key_t key)
void metrics_increase_by(metric_key_t key, int32_t n)
void metrics_reset(void)
void metrics_iterate(void (*callback_each)(metric_key_t key, int32_t value, void *ctx), void *ctx)

Traversing all metrics firing callback.

@warn This function does not guarantee synchronization. Any metrics may be updated while the callback is running.

Parameters:
  • callback_each – callback to be fired every metric

  • ctx – context to be used

size_t metrics_collect(void *buf, size_t bufsize)
size_t metrics_count(void)
void metrics_init(bool force)
const char *metrics_stringify_key(metric_key_t key)

Typedefs

typedef uint16_t metric_key_t

Enums

Values:

Defines

METRICS_USER_DEFINES "metrics.def"
METRICS_VALUE(x) ((int32_t)(x))
METRICS_DEFINE(key) key,

Functions

void metrics_lock_init(void)
void metrics_lock(void)
void metrics_unlock(void)
size_t metrics_encode_header(void *buf, size_t bufsize, uint32_t nr_total, uint32_t nr_updated)

It creates an encoding header.

This function is called internally in metrics_collect().

Parameters:
  • buf[in] buffer

  • bufsize[in] buffer size

  • nr_total[in] the number of metrics declared in metrics.def

  • nr_updated[in] the number of metrics with non-zero value

Returns:

the number of bytes written

size_t metrics_encode_each(void *buf, size_t bufsize, metric_key_t key, int32_t value)

Power Management

Functions

int pm_enter(pm_mode_t mode, uint32_t duration_ms)
int pm_register_entry_callback(pm_mode_t mode, int8_t priority, pm_callback_t func, void *arg)
int pm_register_exit_callback(pm_mode_t mode, int8_t priority, pm_callback_t func, void *arg)
int pm_unregister_entry_callback(pm_mode_t mode, int8_t priority, pm_callback_t func)
int pm_unregister_exit_callback(pm_mode_t mode, int8_t priority, pm_callback_t func)

Typedefs

typedef enum pm_mode pm_mode_t
typedef void (*pm_callback_t)(void *ctx)

Enums

enum pm_mode

Values:

enumerator PM_SLEEP
enumerator PM_SLEEP_DEEP
enumerator PM_SLEEP_SHIP
enumerator PM_RESET_SOFT
enumerator PM_RESET_HARD
enumerator PM_SHUTDOWN

Defines

PM_CALLBACK_MAXLEN 8U
pm_init libmcu_pm_init

pubsub

Overview

Simple usecase

The callback function should be as simple and fast as possible since it runs in the context of a caller(publisher) in sequence, one by one.

pubsub simple usecase
static void hello_callback(void *context, const void *msg, size_t msglen) {
    printf("%.*s\n", msglen, msg);
}
pubsub_subscribe("mytopic", hello_callback, NULL);
pubsub_publish("mytopic", "Hello, World!", strlen("Hello, World!"));

Usecase with queue

Each subscriber runs concurrently as a job while a publisher has the responsiblility to publish a message for jobs to each queues.

pubsub usecase
static void event_callback(void *context, const void *msg, size_t msglen)
{
    queue_t event_queue = (queue_t)context;
    ...
    queue_send(event_queue, new_queue_data);
}

static void job1(uint32_t timeout_ms)
{
    queue_receive(event_queue1, buf, timeout_ms);
}
static void job2(uint32_t timeout_ms)
{
    queue_receive(event_queue2, buf, timeout_ms);
}
static void jobN(uint32_t timeout_ms)
{
    queue_receive(event_queueN, buf, timeout_ms);
}

pubsub_subscribe("mytopic", event_callback, event_queue1);
pubsub_subscribe("mytopic", event_callback, event_queue2);
pubsub_subscribe("mytopic", event_callback, event_queueN);

pubsub_publish("mytopic", data, data_size);

Usecase with Jobqueue as broker

Jobqueue is used as a broker. Both of publishing and subscribing can be done concurrently in another context.

pubsub usecase

Integration Guide

  • PUBSUB_TOPIC_NAME_MAXLEN

  • PUBSUB_MIN_SUBSCRIPTION_CAPACITY

  • PUBSUB_DEBUG

Functions

pubsub_error_t pubsub_publish(const char *topic, const void *msg, size_t msglen)

Publish a message to a topic.

Calling all the callbacks registered in the subscriptions is done in the context of the caller, which takes time to finish all. So a kind of task, such a jobqueue, would help it run in another context.

Parameters:
  • topic[in] is where the message gets publshed to

  • msg[in] A message to publish

  • msglen[in] The length of the message

Returns:

error code in pubsub_error_t

pubsub_subscribe_t pubsub_subscribe_static(pubsub_subscribe_t handle, const char *topic_filter, pubsub_callback_t cb, void *context)

Note

topic_filter should be kept in valid memory space even after registered. Because it keeps dereferencing the pointer of topic_filter ever afterward until unsubscribing.

pubsub_subscribe_t pubsub_subscribe(const char *topic_filter, pubsub_callback_t cb, void *context)
pubsub_error_t pubsub_unsubscribe(pubsub_subscribe_t handle)
int pubsub_count(const char *topic)
pubsub_error_t pubsub_create(const char *topic)
pubsub_error_t pubsub_destroy(const char *topic)
const char *pubsub_stringify_error(pubsub_error_t err)
void pubsub_init(void)
void pubsub_deinit(void)

Typedefs

typedef pubsub_subscribe_static_t *pubsub_subscribe_t
typedef void (*pubsub_callback_t)(void *context, const void *msg, size_t msglen)
union pubsub_subscribe_static_t
#include <pubsub.h>

Public Members

char _size[16]
long _align

Enums

enum pubsub_error_t

Values:

enumerator PUBSUB_SUCCESS = 0
enumerator PUBSUB_ERROR = -1
enumerator PUBSUB_EXIST_TOPIC = -2
enumerator PUBSUB_NO_EXIST_TOPIC = -3
enumerator PUBSUB_NO_MEMORY = -4
enumerator PUBSUB_INVALID_PARAM = -5
enumerator PUBSUB_EXIST_SUBSCRIBER = -6
enumerator PUBSUB_NO_EXIST_SUBSCRIBER = -7

Defines

PUBSUB_TOPIC_NAME_MAXLEN 32
PUBSUB_DEBUG(...)

retry

Overview

Integration Guide

retry_generate_random() and retry_sleep_ms() should be implemented first:

#include "retry_overrides.h"

int retry_generate_random(void) {
    return your_random_function();
}
void retry_sleep_ms(unsigned int msec) {
    your_sleep_function(msec);
}

Usage

struct retry retry;
retry_init(&retry, 5, 30000, 5000, 5000);
do {
    if (connect() == SUCCESS) {
        break;
    }
} while (retry_backoff(&retry) != RETRY_EXHAUSTED);

or

struct retry retry;
retry_init(&retry, 5, 30000, 5000, 5000);
uint32_t next_backoff_ms = retry_backoff_next(&retry));

Time extension

Trace

Integration Guide

The compiler flag -finstrument-functions should be passed to activate tracing functionality.

Add the following line to your CMake file: target_compile_options(${your-target} PRIVATE -finstrument-functions)

or CFLAGS += -finstrument-functions in case of Makefile.

The default number of tracing slots is 128 which can hold up to 128 function calls. The maximum number of slots can be changed by TRACE_MAXLEN define.

Additional Information

To get timestamp, uint32_t trace_get_time(void) should be implemented. Otherwise timestamp will always be 0.

To get stack usage, size_t trace_stack_watermark(void) should be implemented. Otherwise stack usage will always be 0.

To get the TID, void *trace_get_current_thread(void) should be implemented. Otherwise TID will always be NULL.

LIBMCU_NO_INSTRUMENT should be put to prevent infinite recursion when you implement your own ones:

LIBMCU_NO_INSTRUMENT
uint32_t trace_get_time(void) {
    return  (uint32_t)xTaskGetTickCount();
}

Hooks

The below functions will be called every function calls. Those functions can be overridden by creating another functions with the same declaration.

  • void trace_enter_hook(const struct trace *entry)

  • void trace_leave_hook(const struct trace *entry)

Functions

void trace_reset(void)

Reset the call depth and clear the buffer.

void trace_clear(void)

Clear trace buffer.

Note

This does not reset the call depth.

size_t trace_count(void)

Count the number of tracing recorded.

Returns:

The number of tracing

void trace_iterate(trace_callback_t callback, void *ctx, int maxlen)
void trace_enter_hook(const struct trace *entry)
void trace_leave_hook(const struct trace *entry)
uint32_t trace_get_time(void)
size_t trace_get_stack_watermark(void)
void *trace_get_current_thread(void)

Typedefs

typedef void (*trace_callback_t)(const struct trace *entry, void *ctx)
struct trace
#include <trace.h>

Public Members

uint32_t timestamp
void *thread
void *callee
void *caller
size_t stack_usage
uint8_t depth

Defines

TRACE_MAXLEN 128

Interface

ADC

Functions

static inline int adc_enable(struct adc *self)
static inline int adc_disable(struct adc *self)
static inline int adc_channel_init(struct adc *self, adc_channel_t channel)
static inline int adc_calibrate(struct adc *self)
static inline int adc_measure(struct adc *self)
static inline int adc_read(struct adc *self, adc_channel_t channel)
static inline int adc_convert_to_millivolts(struct adc *self, int value)
struct adc *adc_create(uint8_t adc_num)
int adc_delete(struct adc *self)

Enums

enum adc_channel_t

Values:

enumerator ADC_CH_1 = 0x00000001UL
enumerator ADC_CH_2 = 0x00000002UL
enumerator ADC_CH_3 = 0x00000004UL
enumerator ADC_CH_4 = 0x00000008UL
enumerator ADC_CH_5 = 0x00000010UL
enumerator ADC_CH_6 = 0x00000020UL
enumerator ADC_CH_7 = 0x00000040UL
enumerator ADC_CH_8 = 0x00000080UL
enumerator ADC_CH_9 = 0x00000100UL
enumerator ADC_CH_10 = 0x00000200UL
enumerator ADC_CH_11 = 0x00000400UL
enumerator ADC_CH_12 = 0x00000800UL
enumerator ADC_CH_13 = 0x00001000UL
enumerator ADC_CH_14 = 0x00002000UL
enumerator ADC_CH_15 = 0x00004000UL
enumerator ADC_CH_16 = 0x00008000UL
enumerator ADC_CH_17 = 0x00010000UL
enumerator ADC_CH_18 = 0x00020000UL
enumerator ADC_CH_19 = 0x00040000UL
enumerator ADC_CH_20 = 0x00080000UL
enumerator ADC_CH_ALL = 0x7fffffffUL

Defines

adc_enable libmcu_adc_enable
adc_disable libmcu_adc_disable
adc_channel_init libmcu_adc_channel_init
adc_calibrate libmcu_adc_calibrate
adc_channel_t libmcu_adc_channel_t

Flash

Functions

static inline int flash_erase(struct flash *self, uintptr_t offset, size_t size)
static inline int flash_write(struct flash *self, uintptr_t offset, const void *data, size_t len)
static inline int flash_read(struct flash *self, uintptr_t offset, void *buf, size_t len)
static inline size_t flash_size(struct flash *self)
struct flash *flash_create(int partition)
int flash_delete(struct flash)

GPIO

Functions

static inline int gpio_enable(struct gpio *self)
static inline int gpio_disable(struct gpio *self)
static inline int gpio_set(struct gpio *self, int value)
static inline int gpio_get(struct gpio *self)
static inline int gpio_register_callback(struct gpio *self, gpio_callback_t cb, void *cb_ctx)
struct gpio *gpio_create(uint16_t pin)
void gpio_delete(struct gpio *self)

I2C

Functions

static inline int i2c_enable(struct i2c *self)
static inline int i2c_disable(struct i2c *self)
static inline int i2c_read(struct i2c *self, uint8_t slave_addr, void *buf, size_t bufsize, uint32_t timeout_ms)
static inline int i2c_write(struct i2c *self, uint8_t slave_addr, const void *data, size_t data_len, uint32_t timeout_ms)
static inline int i2c_read_reg(struct i2c *self, uint8_t slave_addr, uint32_t reg_addr, uint8_t reg_addr_bits, void *buf, size_t bufsize, uint32_t timeout_ms)
static inline int i2c_write_reg(struct i2c *self, uint8_t slave_addr, uint32_t reg_addr, uint8_t reg_addr_bits, const void *data, size_t data_len, uint32_t timeout_ms)
struct i2c *i2c_create(uint8_t channel)
void i2c_delete(struct i2c *self)

KVStore

Functions

static inline int kvstore_open(struct kvstore *self, char const *ns)
static inline void kvstore_close(struct kvstore *self)
static inline int kvstore_write(struct kvstore *self, char const *key, void const *value, size_t size)
static inline int kvstore_read(struct kvstore *self, char const *key, void *buf, size_t size)
static inline int kvstore_clear(struct kvstore *self, char const *key)

Functions

struct kvstore *nvs_kvstore_new(void)

Create a non-volatile storage instance.

Note

Initialization of the storage must be done before calling this function.

Returns:

an instance

int nvs_kvstore_count(void)

Functions

struct kvstore *flash_kvstore_new(struct flash *flash, struct flash *scratch)

PWM

Functions

static inline int pwm_enable(struct pwm *self)
static inline int pwm_disable(struct pwm *self)
static inline int pwm_start(struct pwm *self, uint32_t freq_hz, uint32_t duty_millipercent)
static inline int pwm_update_frequency(struct pwm *self, uint32_t hz)
static inline int pwm_update_duty(struct pwm *self, uint32_t millipercent)
static inline int pwm_stop(struct pwm *self)
struct pwm *pwm_create(uint8_t ch)
int pwm_delete(struct pwm *self)

SPI

Functions

static inline int spi_enable(struct spi *self)
static inline int spi_disable(struct spi *self)
static inline int spi_write(struct spi *self, const void *data, size_t data_len)
static inline int spi_read(struct spi *self, void *buf, size_t bufsize)
static inline int spi_writeread(struct spi *self, const void *txdata, size_t txdata_len, void *rxbuf, size_t rxbuf_len)
struct spi *spi_create(uint8_t channel)
void spi_delete(struct spi *self)

Timer

Functions

static inline int timer_enable(struct timer *self)
static inline int timer_disable(struct timer *self)
static inline int timer_start(struct timer *self, uint32_t timeout_ms)
static inline int timer_restart(struct timer *self, uint32_t timeout_ms)
static inline int timer_stop(struct timer *self)
struct timer *timer_create(bool periodic, timer_callback_t callback, void *arg)
int timer_delete(struct timer *self)

UART

Functions

static inline int uart_enable(struct uart *self, uint32_t baudrate)
static inline int uart_disable(struct uart *self)
static inline int uart_write(struct uart *self, const void *data, size_t data_len)
static inline int uart_read(struct uart *self, void *buf, size_t bufsize)
static inline int uart_register_rx_callback(struct uart *self, uart_rx_callback_t cb, void *cb_ctx)
struct uart *uart_create(uint8_t channel)
void uart_delete(struct uart *self)

WiFi

Functions

static inline int wifi_connect(struct wifi *self, const struct wifi_conn_param *param)
static inline int wifi_disconnect(struct wifi *self)
static inline int wifi_scan(struct wifi *self)
static inline int wifi_enable(struct wifi *self)
static inline int wifi_disable(struct wifi *self)
static inline int wifi_get_status(struct wifi *self, struct wifi_iface_info *info)
static inline int wifi_register_event_callback(struct wifi *self, enum wifi_event event_type, const wifi_event_callback_t cb, void *cb_ctx)
struct wifi *wifi_create(int id)
int wifi_delete(struct wifi *self)

Enums

enum wifi_event

Values:

enumerator WIFI_EVT_UNKNOWN
enumerator WIFI_EVT_CONNECTED
enumerator WIFI_EVT_DISCONNECTED
enumerator WIFI_EVT_SCAN_RESULT
enumerator WIFI_EVT_SCAN_DONE
enumerator WIFI_EVT_STARTED
enumerator WIFI_EVT_STOPPED
enumerator WIFI_EVT_ANY
enum wifi_frequency_band

Values:

enumerator WIFI_FREQ_2_4_GHZ
enumerator WIFI_FREQ_5_GHZ
enumerator WIFI_FREQ_6_GHZ
enum wifi_security

Values:

enumerator WIFI_SEC_TYPE_UNKNOWN
enumerator WIFI_SEC_TYPE_NONE
enumerator WIFI_SEC_TYPE_WEP
enumerator WIFI_SEC_TYPE_PSK
enumerator WIFI_SEC_TYPE_PSK_SHA256
enumerator WIFI_SEC_TYPE_PSK_SAE
enum wifi_mfp

Values:

enumerator WIFI_MFP_OPTIONAL
enumerator WIFI_MFP_REQUIRED
enumerator WIFI_MFP_DISABLED

Defines

WIFI_SSID_MAX_LEN 32U
WIFI_MAC_ADDR_LEN 6U