Skip to content

Instantly share code, notes, and snippets.

@ikskuh
Created October 25, 2020 19:39
Show Gist options
  • Save ikskuh/a030a2be23fcea55dd047ff91617f8a2 to your computer and use it in GitHub Desktop.
Save ikskuh/a030a2be23fcea55dd047ff91617f8a2 to your computer and use it in GitHub Desktop.
Embedded Device Event Loop
pub fn main() !void {
SysTick.init(1_000);
EventLoop.init();
var serial_frame = async doSerialLoop();
var blinky_frame = async doBlinkyLoop();
EventLoop.run();
try nosuspend await serial_frame;
nosuspend await blinky_frame;
}
fn doBlinkyLoop() void {
while (true) {
IO_BEL_OUT.set();
EventLoop.waitForMillis(500);
IO_BEL_OUT.clear();
EventLoop.waitForMillis(250);
}
}
fn doSerialLoop() !void {
var buffer: [256]u8 = undefined;
var out = Serial.writer(); // these use EventLoop.waitForRegister internally
var in = Serial.reader(); // these use EventLoop.waitForRegister internally
while (true) {
try out.writeAll("Enter your name: ");
const name = (try in.readUntilDelimiterOrEof(&buffer, '\r')) orelse unreachable;
try out.print("\r\nYour name is {}. Hello, {}!\r\n", .{ name, name });
}
}
const EventLoop = struct {
// Combination of a resume condition and a resume location
const SuspendedTask = struct {
// Where do we resume
frame: anyframe,
// when this condition is met?
condition: WaitCondition,
};
/// A generic condition that will trigger a resume
/// On PC, this is likely a file descriptor and a poll event
const WaitCondition = union(enum) {
register8: Register(u8),
register16: Register(u16),
register32: Register(u32),
time: u32,
fn Register(comptime T: type) type {
return struct {
register: *volatile T,
mask: T,
value: T,
};
}
fn isMet(cond: @This()) bool {
return switch (cond) {
.register8 => |reg| (reg.register.* & reg.mask) == reg.value,
.register16 => |reg| (reg.register.* & reg.mask) == reg.value,
.register32 => |reg| (reg.register.* & reg.mask) == reg.value,
.time => |time| SysTick.get() >= time,
};
}
};
// On embedded, we don't do dynamic allocation.
// This is usually a linked list or ArrayList
var tasks: [64]SuspendedTask = undefined;
var task_count: usize = 0;
pub fn waitFor(condition: WaitCondition) void {
// don't suspend if we already meet the condition
// if (condition.isMet())
// return;
std.debug.assert(task_count < tasks.len);
var offset = task_count;
tasks[offset] = SuspendedTask{
.frame = @frame(),
.condition = condition,
};
task_count += 1;
suspend;
}
pub fn waitForRegister(comptime Type: type, register: *volatile Type, mask: Type, value: Type) void {
std.debug.assert((mask & value) == value);
var reg = WaitCondition.Register(Type){
.register = register,
.mask = mask,
.value = value,
};
waitFor(switch (Type) {
u8 => WaitCondition{
.register8 = reg,
},
u16 => WaitCondition{
.register16 = reg,
},
u32 => WaitCondition{
.register32 = reg,
},
else => @compileError("Type must be u8, u16 or u32!"),
});
}
pub fn waitForMillis(delta: u32) void {
waitFor(WaitCondition{
.time = SysTick.get() + delta,
});
}
pub fn init() void {
task_count = 0;
}
pub fn run() void {
while (task_count > 0) {
std.debug.assert(task_count <= tasks.len);
var i: usize = 0;
while (i < task_count) : (i += 1) {
if (tasks[i].condition.isMet()) {
var frame = tasks[i].frame;
if (i < (task_count - 1)) {
std.mem.swap(SuspendedTask, &tasks[i], &tasks[task_count - 1]);
}
task_count -= 1;
resume frame;
break;
}
}
}
}
};
/// This is a component of the Cortex M3 processor,
/// it will call handleInterrupt every with frequency `freq` after a call to `init(freq)`
const SysTick = struct {
/// Time since start in "ticks"
var counter: u32 = 0;
fn init(comptime freq: u32) void {
lpc.NVIC_SetHandler(.SysTick, handleInterrupt);
lpc.SysTick_Config(F_CPU / freq) catch unreachable;
}
fn get() u32 {
return @atomicLoad(u32, &SysTick.counter, .SeqCst);
}
fn handleInterrupt() callconv(.Interrupt) void {
_ = @atomicRmw(u32, &counter, .Add, 1, .SeqCst);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment