Software Design & Structure
like how to software ig…disclaimer half of ts is chat so use ur brain
Repo Setup
When creating a new Github repo, you should add a branch ruleset to require two required approvals to merge into main. Go to your repository’s settings page and configure the ruleset like this:
PR template?
CI/CD? ideally we should setup a build workflow at least. theres prob a way to github template with build actions…
File Structure
Top-level file structure for software repos should look something like this:
Apps - contains main.c, error handlers, any high-level common functionality
Drivers - contains low-level device drivers
Embedded-Sharepoint (submodule) - contains BSP, linker files, flash script, etc
Tasks - contains FreeRTOS tasks
Tests - contains unit & integration tests
.gitignore
Makefile
Each of Apps, Drivers, and Tasks should have Src (source) and Inc (include) folders
Embedded-Sharepoint should almost always be checked out to main unless there’s a reason you need a particular branch (untested features, etc). Your gitignore (example) should be set up for C and set to ignore the Build directory to avoid committing build artifacts. We also have an example Makefile to get started which should build/flash your code with relatively few changes.
Make sure to change the PROJECT_TARGET variable to your chip (e.g. stm32f446ret)
Naming
Drivers should generally be named after the device they’re written for, such as “EMC2305.c” or “ADS131M08.h.” Tasks are where your code’s logic should be, they are scheduled to execute or suspend based on priorities and some magic . Name tasks using the convention FunctionTask.c, for example “InitTask.c” or “TemperatureMonitorTask.c.” InitTask is special in that it initializes all the other tasks and then deletes itself. Finally, in order to run tests using the Embedded-Sharepoint Makefile, they must be named like Test_Type_Name.c, for example “Test_Driver_Contactors.c” or “Test_Task_Timers.c.”
For reference, a good example repo is
GitHub - lhr-solar/BPS-Leader: Reads voltage, temperature, and current of the main battery pack from the VoltTemp and Amperes boards to ensure the battery remains in a safe state.. However, use your discretion and make changes as needed to fit your board/system’s requirements.
Branching
Most branches fall into one of these categories:
Feature - adds new functionality to a release (merge into Release)
Bugfix - addresses bugs found when developing a release (merge into Release)
Hotfix - addresses bugs found in production/released environment (merge into main)
Release - adds a new major version of the software (merge into main)
Branch names should be formatted as Type/Name, for example “Feature/NewPinout” or “Hotfix/CAN-Init.” Examples of release times could be unveiling, safety verification, powered run, competition, etc.
Testing
Unit testing is a software testing technique where individual components or functions of a program are tested in isolation to ensure they work as expected. Each test checks a small, specific part of the code—typically a single function—with controlled inputs and expected outputs. This helps catch bugs early, improves code reliability, and makes future changes safer by verifying that existing functionality remains correct. - chat, 2025
Basically, you should write unit tests for every piece of major functionality in your code. As a byproduct, this encourages writing modular code that can be run and tested independently of other parts of the system. For example, we write tests for device drivers, CAN message integration, state machines, etc. When testing on hardware, you should use LEDs or another physical indicator of the test succeeding/failing.
Integration testing checks how different pieces of a system work together. Unlike unit tests, which isolate functions or modules, integration tests verify that combined components—like functions, classes, or external systems—interact correctly. These tests help catch issues that arise from incorrect assumptions between parts of the system, such as data mismatches or communication errors. - chat
Extensive examples of unit/integration tests can be found in the Daybreak BPS and Controls software repos. Keep in mind these were written for Micrium RTOS so there are some differences from the current FreeRTOS architecture.
Review Process
When you feel like your code is ready for review, make a pull request to merge your changes into the relevant release branch. In your PR notes, describe what you added/changed/fixed and any remaining todos or questions for the reviewers.
Before requesting a review, make sure to test on hardware and confirm functionality
You should assign reviewers in Github, which sends them an email, but make sure to drop your PR link in the #review-request slack channel and mention your 2-3 reviewers there as well.
Code review is an ongoing process - don’t expect to be able to merge the first time, you’ll probably have to make changes and iterate a few times before getting approvals
Releases
Software is ready to be released when it has been approved by peer-reviewers, merged into main, and tested in a hardware-in-the-loop (HITL) environment with other relevant boards/systems. Tagging a release means we’re confident that code can be run reliably in a safety-critical environment and will behave as expected by other systems. This also helps us keep track of what version of our code is running on the car at any time.
To tag a release on Github, follow this guide and make sure to add a meaningful description.
idk what we wanna do for version numbers? can do semver
Documentation
Every repo should have a readme - if you’re working on a dedicated firmware repo, this should be in the top-level directory, otherwise it should be in your Firmware directory. At a minimum, you should include a few sentences on what the code does, how it interacts with other boards (CAN/other signals), and how to build and run tests on hardware.
All significant (think if a new freshman couldn’t understand) functions should be documented above the function signature as follows:
/**
* @brief Description of what the function does and any notes
* @param ParameterName Description of the parameter
* (If needed - Possible values for the parameter)
* @return The return value of the function
*/Examples:
/**
* @brief Helper function for reading precharge completion state
* Should only be called if contactor is a precharge contactor
* @param contactor the contactor
* (MOTOR_PRECHARGE_CONTACTOR/ARRAY_PRECHARGE_CONTACTOR)
* @return The contactor's precharge completion state from the output of hardware comparison
*/
bool getPrecharge(contactor_enum_t contactor) {
// blah blah
}/**
* @brief Writes data to a specified pin
* @param led The led to write to
* @param state true=ON or false=OFF
* @return None
*/
void Status_Leds_Write(status_led_t led, bool state) {
// blah blah
}