Skip to content

Writing guide

Mason Jones edited this page Aug 22, 2026 · 3 revisions

Design

Don't overarchitect

Code should be easy to read, and minimize indirection (i.e. putting things behind functions or classes when they could be accessed directly).

Isolate logic and IO

Isolate all complicated logic behind a class, and avoid using IO operations in that class (time, GPIO pins, sensor readings, etc). Here's an annotated version of the main loop to demonstrate this isolation:

void loop() {
    uint32_t current_time_ms = millis(); /* We get the current time here, not inside the ECU logic. */

    /* Receive a message and have the ECU process it. */
    CAN_message_t rmsg;
    if (MotorCAN.read(rmsg)) {
        /* Here we inject the received message into the ECU, we don't receive the message inside the ECU.
         * This isolates the CAN interface from the logic. */
        ECU.processMessage(current_time_ms, rmsg);
    }

    /* Generate all outgoing messages and send each. */
    while (true) {
        std::optional<CAN_message_t> to_send = ECU.pollCan(current_time_ms); /* Again, isolate CAN. */
        if (to_send.has_value()) {
            MotorCAN.write(*to_send);
        } else {
            break;
        }
    }

    /* Here's an example of isolating GPIO from logic. Note that we call the values `horn_on` and
     * `brake_light_on`, so it's clear what the purpose of the field is. */
    auto state = ECU.pollGpioState(current_time_ms);
    digitalWrite(HORN_PIN, state.horn_on);
    digitalWrite(BRAKE_LIGHT_PIN, state.brake_light_on);
}

This style makes it much much easier to write tests against the logic, and avoids the need for mocking functions (mocking is very brittle in my opinion).

Comments

We have a lot of people coming and leaving with this project, so commenting is vital. So how does one write a good comment? The best guiding principle I've found is write why comments, not what comments.

Example of a bad comment:

/* Track implausibilities. */
struct ImplausibilityDetails {
    LineInfo line_info;
    uint32_t happened_at_ms;
    AssertCode code;
};

And a good comment:

/* If an implausibility occurs (as defined in the 2026 rules, section T.4.2.4),
 * we want to save the details of the implausibility. That way, if the
 * implausibility lasts too long, we can report what implausibility it was and
 * where it came from. */
struct ImplausibilityDetails {
    LineInfo line_info;
    uint32_t happened_at_ms;
    AssertCode code;
};

Note how the good comment explains why we need to track implausibilities in the first place. This way when someone gets to unfamiliar code, they can understand why something is the way it is. What comments only make it harder to read code, since you have to read both the code and the comment, and the comment will often get out of sync with the code.

Documentation

Clone this wiki locally