rkalnins / rmkernel

Realtime Micro Kernel -- Event-driven Run-to-Completion RTOS with Active Objects, Timed Events, Memory Pools, and Message Queues

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Realtime Micro Kernel

See the University of Michigan Fall 2021 EECS 373 final project WarehouseProject-EECS373/zumo-controller for usage example.

In the example project, a bit of a different source structure is used, specifically

  • See src/os_port_arm_m4.c for borrowed and slightly modified code from Quantum Leap's QP-nano (GPLv3)
  • src/app_defs.h contains message definitions
  • src/main.c contains active object declarations

Currently, os_port_arm_m4.c in the example project is ports/arm-cortex-m4/port.c in rmkernel.

Features

  • Active Objects
    • Message queues
    • Variable sized, custom messages
  • Periodic and single timed events
  • Memory pools
  • Command-based hierarchical state machine framework
    • Commands
    • Instant commands

Usage

Active Objects

// app_definitions.h: global definitions
#include <os.h>

#define OBJECT_QUEUE_SIZE 16
#define OBJECT_ID 0
#define OBJECT_PRIORITY 1

ACTIVE_OBJECT_EXTERN(example_object, OBJECT_QUEUE_SIZE)
// main.c
#include "app_definitions.h"

ACTIVE_OBJECT_DECL(example_object, OBJECT_QUEUE_SIZE)

void ObjectEventHandler(Message_t *msg)
{  
}

int main()
{
    AO_INIT(example_object, OBJECT_PRIORITY, ObjectEventHandler, OBJECT_QUEUE_SIZE, OBJECT_ID);
}

Custom Messages and Message Queues

// app_definitions.h: global definitions

#define EXAMPLE_MSG_ID  0x100

typedef struct ExampleMessage_s
{
    Messasge_t base;
    uint32_t data;
} ExampleMessage_t;
// isr_example.c
#include "app_definitions.h"
#include <os.h>
#include <os_msg.h>


void ExampleISR()
{
    STATIC_ASSERT(sizeof(ExampleMessage_t) <= OS_MESSAGE_MAX_SIZE);

    // send a message to example_object
    ExampleMessage_t msg;
    msg.base.id = EXAMPLE_MSG_ID;
    msg.base.msg_size = sizeof(ExampleMessage_t);
    msg.data = 543210;

    MsgQueuePut(&example_object, &msg);
}
// example_object.c
#include "app_definitions.h"

void ObjectEventHandler(Message_t *msg)
{
    if ( EXAMPLE_MSG_ID == msg->id)
    {
        ExampleMessage_t *ex_msg = (ExampleMessage_t*)msg;
        // handle
    }
}

Timed and Periodic Events

#define DELAY_OR_PERIOD    500 // ms
#define EVENT_TYPE         TIMED_EVENT_SINGLE_TYPE // or TIMED_EVENT_PERIODIC_TYPE

TimedEventSimple_t event;
Message_t timed_event_msg = {.id = 0x101, .msg_size = sizeof(Message_t)};

TimedEventSimpleCreate(&event, &state_ctl_ao, &timed_event_msg, DELAY_OR_PERIOD, EVENT_TYPE);
SchedulerAddTimedEvent(&event);

Memory Pools

Can be accessed using a 16-bit key

// getting a memory block pointer
OSStatus_t status;
uint16_t key;

uint8_t* block_ptr = OSMemoryBlockNew(&key, MEMORY_BLOCK_32, &status); // _64, _128, _512 sizes available as well
// getting block
uint8_t* buffer = OSMemoryBlockGet(key);

// use, make sure to free when done
OSMemoryFreeBlock(key);

State Machine Framework

We create three commands: A, B, and C. A, B, and C are chained together in that order. Command A's implementation is expanded. To create nested hierarchies, create StateMachine_t in the CommandX_t struct. Initialize it and then start the state machine the command in the command's on_Start function. on_Message should pass the given message down into the nested state machine (i.e. treat on_Message like an event handler as seen in state_controller.c).

// app_definitions.h
#define DONE_MSG_ID 0x102
// cmd_a.h
#include "app_definitions.h"
#include <state_machine.h>

typedef struct CommandA_s
{
    Command_t base;
    // add state machine instance here to create nested hierarchies
    // instance data
} CommandA_t;

extern void CmdAInit(CommandA_t *cmd, /* init data */, Command_t *next);

// instance data here is optional, if anything needs to be passed down to the Command instances
extern void CmdA_OnStart(CommandA_t *cmd, void *instance_data);
extern bool CmdA_OnMessage(CommandA_t *cmd, Message_t *msg, void *instance_data);
extern void CmdA_OnEnd(CommandA_t *cmd, void *instance_data);
// cmd_a.c

#include "cmd_a.h"

extern void CmdA_Init(CommandA_t *cmd, /* init data */, Command_t *next)
{
    cmd->base.on_Start = CmdA_OnStart;
    cmd->base.on_Message = CmdA_OnMessage;
    cmd->base.on_End = CmdA_OnEnd;

    // waits until OnMessage returns true, COMMAND_ON_END_INSTANT immediately goes to next state
    // after running OnStart
    cmd->base.end_behavior = COMMAND_ON_END_WAIT_FOR_END;

    // chain next command to this one, NULL will be end of chain
    cmd->base.next = next;

    /* set init/instance data */
}

extern void CmdA_OnStart(CommandA_t *cmd, void *instance_data)
{
    // runs when command starts    
}

extern bool CmdA_OnMessage(CommandA_t *cmd, Message_t *msg, void *instance_data)
{
    // return true if done so state machine can advance to next state
    return DONE_MSG_ID == msg->id;
}

extern void CmdA_OnEnd(CommandA_t *cmd, void *instance_data)
{
    // runs when command is done (after OnMessage returns true)
}
// state_controller.c
#include "app_definitions.h"
#include <state_machine.h>

#include "cmd_a.h"
#include "cmd_b.h"
#include "cmd_c.h"

static StateMachine_t sm;

static CommandA_t cmd_a;
static CommandB_t cmd_b;
static CommandC_t cmd_c;

void Init()
{
    CmdA_Init(&cmd_a, /* init data */, (Command_t*) cmd_b); // b follows a
    CmdB_Init(&cmd_b, /* init data */, (Command_t*) cmd_c); // c follows b
    CmdC_Init(&cmd_c, /* init data */, (Command_t*) NULL); // NULL pointer ends sequence

    StateMachineInit(&sm, (Command_t*) cmd_a); // starting with cmd_a
    StateMachineStart(&sm, NULL);
}

void EventHandler(Message_t *msg)
{
    StateMachineStep(&sm, msg, NULL);
}

Supported Platforms

Tested and developed on STM32 platforms using ObKo/stm32-cmake

  • ARM Cortex-M4 (STM32L4R5ZI, STM32F401RE)

STM32 Board Notes

UART

  • For STM32L4R5ZI VddIO2 must be enabled for LPUART to work on STM32L4R5. Enable PWR clock beforehand.

Clocks, Timing

  • SysTick_Handler runs at lower interrupt priority for rmkernel. However, STM32 HAL expects to hook into the 1ms tick. Therefore, we need an alternative to SysTick_Handler for the STM32 HAL. The solution was to sacrifice TIM2 to the HAL and configure it as a 1ms clock to drive the millisecond-precision OSTime and HAL time. Can hook onto the __weakly defined HAL_IncTick, HAL_InitTick, and HAL_GetTick to make this happen. See WarehouseProject-EECS373/zumo-controller/src/rmk_hal_clock_cfg.c/h for an implementation of this.

About

Realtime Micro Kernel -- Event-driven Run-to-Completion RTOS with Active Objects, Timed Events, Memory Pools, and Message Queues

License:GNU General Public License v3.0


Languages

Language:C 95.2%Language:CMake 3.3%Language:Makefile 1.1%Language:Shell 0.5%