Skip to content

Start on new structure #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ Cargo.lock
.DS_Store
*~


# Vim temporary files
*.swp
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ build-mgmt:
flash-mgmt:
cd mgmt && cargo flash --release --chip STM32F072CBTx

# XXX(RLB) This line has a bunch of special cases for my laptop that probably
# won't work elsewhere
flasher-mgmt:
cd mgmt && cargo objcopy --release --target=thumbv6m-none-eabi -- -O binary target/mgmt.bin
python3 ../hactar/software/flasher/main.py --baud=115200 --chip="mgmt" --port=/dev/tty.usbserial-10 \
-bin=mgmt/target/mgmt.bin

run-mgmt:
echo run "openocd -f mgmt/openocd.cfg" in background
echo run something like "screen /dev/tty.usbserial-120 115200" in another window
Expand Down
18 changes: 6 additions & 12 deletions mgmt/mem072.x
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ SECTIONS
*(.rodata .rodata.*);
} > FLASH

.data : ALIGN(4) /* AT(ADDR(.rodata) + SIZEOF(.rodata)) */
.data : ALIGN(4)
{
_sdata = .;
*(.data .data.*);
_edata = .;
} > RAM AT > FLASH
} > RAM AT > FLASH

_sidata = LOADADDR(.data);

Expand All @@ -49,16 +49,10 @@ SECTIONS
_ebss = .;
} > RAM

.heap_start :
{
_heap_start = .;
. = ALIGN(4);
. = . + _Heap_Size;
_stack_reserve_start = .;
. = . + _Stack_Size;
_stack_reserve_end = .;
. = ALIGN(4);
} > RAM
/* Heap and stack symbols defined without creating sections */
_heap_start = .;
_stack_reserve_start = _heap_start + _Heap_Size;
_stack_reserve_end = _stack_reserve_start + _Stack_Size;

.stack_sizes (INFO) :
{
Expand Down
8 changes: 8 additions & 0 deletions neo/mgmt-cots/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[target.thumbv6m-none-eabi]
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=--nmagic",
]

[build]
target = "thumbv6m-none-eabi"
8 changes: 8 additions & 0 deletions neo/mgmt-cots/.claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(cargo:*)"
],
"deny": []
}
}
13 changes: 13 additions & 0 deletions neo/mgmt-cots/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "mgmt-cots"
version = "0.1.0"
edition = "2024"

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
stm32f0xx-hal = { version = "0.18", features = ["stm32f072", "rt"] }
embedded-dma = "0.2"
nb = "1"
heapless = { version = "0.8", features = ["portable-atomic-unsafe-assume-single-core"] }
8 changes: 8 additions & 0 deletions neo/mgmt-cots/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FLASHER=../../../hactar/software/flasher/main.py
PORT=/dev/tty.usbserial-10

.PHONY: flash

flash:
cargo objcopy --release --target=thumbv6m-none-eabi -- -O binary target/mgmt.bin
python3 ${FLASHER} --baud=115200 --chip=mgmt --port=${PORT} --bin=target/mgmt.bin
75 changes: 75 additions & 0 deletions neo/mgmt-cots/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
MGMT with [COTS] parts
======================

In the interest of getting the firmware design moving quickly, this crate is an
effort to get the management chip running using an [off-the-shelf HAL] as much as
possible. With the idea that we can strip it down to something more minimal
later, once we have things working.

## Architecture

In brief:

* The `board` module defines how the hardware board is put together.
* The `app` module defines the application logic and the traits of the board
that it depends on.
* The `main` file instantiates the board and the app, and organizes the
execution in the main function and ISRs.

```
+-----------+ +-----------+ +-----------+
| | | | | |
| |-------Events------>| |-------Events------>| |
| Board | | Main | | App |
| |<------------------------------------Capabilities----| |
| | | | | |
+-----------+ +-----------+ +-----------+
^
|
|
Events
|
|
+-----------+
| |
| |
| ISRs |
| |
| |
+-----------+
```

The main logic of the application is in the function `App::handle(event,
capbilities...)`.
The application takes in events that reflect inputs from the hardware, and acts
on them using the capabilities. The capabilities are defined using traits,
which are defined in the `app` module and implemented in the `board` module.

In `main.rs`, the board and app are instantiated, as well as a queue of events.
ISRs asynchronously add events to the queue, and the main loop polls for
synchronous events and triggers the app to process events from the queue.

The idea is that ultimately, the app could be pulled out into a separate crate,
and instantiated on different hardware. The app crate would define
hardware-independent interfaces and logic. The device-specific crate would
have the app crate as a dependency. It would implement the required interfaces
(as the `board` module does here) and instantiate the app with inputs from the
board (as the `main` module does here).

## What it actually does

Right now, the firmware does two things, blink and echo.

The blinking LED demonstrates asynchronous events. A 1Hz timer is set, which
causes the TIM7 interrupt to fire. That ISR adds a `Timer1Hz` event to the
queue. When the app processes that event shortly thereafter, an reference to
the LED is passed as a capability, which the app uses to toggle the LED.

The echo functionality echos text from the UART1 back to UART, with "hello: "
prepended. This illustrates synchronous events, since the UART receiver has to
be polled. It also illustrates how the app can use internal state, as it
maintains an internal buffer for characters received from UART. The send/Tx
side of the UART channel is passed to the app as a capability.

[COTS]: https://en.wikipedia.org/wiki/Commercial_off-the-shelf
[off-the-shelf HAL]: https://docs.rs/stm32f0xx-hal
11 changes: 11 additions & 0 deletions neo/mgmt-cots/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
MEMORY
{
/* NOTE K = KiBi = 1024 bytes */
FLASH : ORIGIN = 0x08000000, LENGTH = 128K
RAM : ORIGIN = 0x20000000, LENGTH = 16K
}

/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
43 changes: 43 additions & 0 deletions neo/mgmt-cots/src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use core::fmt::Write;

pub enum Event {
ConsoleRx(u8),
Timer1Hz,
}

pub trait Toggle {
fn toggle(&mut self);
}

#[derive(Default)]
pub struct App {
line: heapless::String<128>,
}

impl App {
pub fn timer_1hz(&mut self, led: &mut dyn Toggle) {
led.toggle();
}

pub fn handle_byte(&mut self, byte: u8, tx: &mut dyn Write) {
// Still building string
if byte != b'\r' && self.line.len() < self.line.capacity() {
self.line.push(byte as char).ok();
return;
}

// End-of-line => send response
tx.write_str("hello: ").ok();
tx.write_str(self.line.as_str()).ok();
tx.write_str("\r\n").ok();

self.line.clear();
}

pub fn handle(&mut self, event: Event, tx: &mut dyn Write, led_a: &mut dyn Toggle) {
match event {
Event::ConsoleRx(b) => self.handle_byte(b, tx),
Event::Timer1Hz => self.timer_1hz(led_a),
}
}
}
98 changes: 98 additions & 0 deletions neo/mgmt-cots/src/board/led.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::app::Toggle;
use stm32f0xx_hal::prelude::_embedded_hal_gpio_OutputPin as OutputPin;

#[allow(dead_code)] // Don't warn if we don't use every color
#[derive(Copy, Clone)]
pub enum Color {
Black,
White,
Red,
Green,
Blue,
Teal,
Yellow,
Purple,
}

impl Color {
fn rgb(&self) -> (bool, bool, bool) {
match self {
Self::Black => (false, false, false),
Self::Red => (true, false, false),
Self::Green => (false, true, false),
Self::Blue => (false, false, true),
Self::Teal => (false, true, true),
Self::Purple => (true, false, true),
Self::Yellow => (true, true, false),
Self::White => (true, true, true),
}
}
}

trait Set {
fn set(&mut self, high: bool);
}

impl<T: OutputPin> Set for T {
fn set(&mut self, high: bool) {
let _ = if high {
self.set_high()
} else {
self.set_low()
};
}
}

pub struct Led<R, G, B, const INVERT: bool = false> {
on: bool,
color: Color,
r: R,
g: G,
b: B,
}

impl<R, G, B, const INVERT: bool> Led<R, G, B, INVERT>
where
R: OutputPin,
G: OutputPin,
B: OutputPin,
{
pub fn new(r: R, g: G, b: B) -> Self {
Self {
on: false,
color: Color::Black,
r,
g,
b,
}
}

fn raw_set(&mut self, c: Color) {
let (r, g, b) = c.rgb();
self.r.set(r ^ INVERT);
self.g.set(g ^ INVERT);
self.b.set(b ^ INVERT);
}

pub fn set(&mut self, c: Color) {
self.on = true;
self.color = c;
self.raw_set(c);
}
}

impl<R, G, B, const INVERT: bool> Toggle for Led<R, G, B, INVERT>
where
R: OutputPin,
G: OutputPin,
B: OutputPin,
{
fn toggle(&mut self) {
self.on = !self.on;
if self.on {
self.raw_set(self.color);
} else {
self.raw_set(Color::Black);
}
}
}
Loading