Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rkalnins/rmkernel
Realtime Micro Kernel -- Event-driven Run-to-Completion RTOS with Active Objects, Timed Events, Memory Pools, and Message Queues
https://github.com/rkalnins/rmkernel
active-object arm cortex-m embedded event-driven rtos state-machine stm32
Last synced: 3 months ago
JSON representation
Realtime Micro Kernel -- Event-driven Run-to-Completion RTOS with Active Objects, Timed Events, Memory Pools, and Message Queues
- Host: GitHub
- URL: https://github.com/rkalnins/rmkernel
- Owner: rkalnins
- License: gpl-3.0
- Created: 2021-11-04T03:19:33.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2022-01-09T20:04:30.000Z (about 3 years ago)
- Last Synced: 2024-10-11T03:14:18.459Z (4 months ago)
- Topics: active-object, arm, cortex-m, embedded, event-driven, rtos, state-machine, stm32
- Language: C
- Homepage:
- Size: 95.7 KB
- Stars: 7
- Watchers: 2
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Realtime Micro Kernel
See the University of Michigan Fall 2021 EECS 373 final project [WarehouseProject-EECS373/zumo-controller](https://github.com/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 declarationsCurrently, `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
```cpp
// app_definitions.h: global definitions
#include#define OBJECT_QUEUE_SIZE 16
#define OBJECT_ID 0
#define OBJECT_PRIORITY 1ACTIVE_OBJECT_EXTERN(example_object, OBJECT_QUEUE_SIZE)
``````cpp
// 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
```cpp
// app_definitions.h: global definitions#define EXAMPLE_MSG_ID 0x100
typedef struct ExampleMessage_s
{
Messasge_t base;
uint32_t data;
} ExampleMessage_t;```
```cpp
// isr_example.c
#include "app_definitions.h"
#include
#includevoid 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);
}
``````cpp
// 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
```cpp
#define DELAY_OR_PERIOD 500 // ms
#define EVENT_TYPE TIMED_EVENT_SINGLE_TYPE // or TIMED_EVENT_PERIODIC_TYPETimedEventSimple_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
```cpp
// 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
``````cpp
// 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`).```cpp
// app_definitions.h
#define DONE_MSG_ID 0x102
``````cpp
// cmd_a.h
#include "app_definitions.h"
#includetypedef 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);
``````cpp
// 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)
}```
```cpp
// state_controller.c
#include "app_definitions.h"
#include#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 sequenceStateMachineInit(&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`](https://github.com/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 `__weak`ly defined `HAL_IncTick`, `HAL_InitTick`, and `HAL_GetTick` to make this happen. See [`WarehouseProject-EECS373/zumo-controller/src/rmk_hal_clock_cfg.c/h`](https://github.com/WarehouseProject-EECS373/zumo-controller/blob/main/src/rmk_hal_clock_cfg.c) for an implementation of this.