Created
October 25, 2020 19:39
-
-
Save ikskuh/a030a2be23fcea55dd047ff91617f8a2 to your computer and use it in GitHub Desktop.
Embedded Device Event Loop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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