Skip to content

Projects

  • asp Assembler

    March 2025, Personal Project


    Assemble machine code for a stepper motor ASIP. Written in Rust.

    Read more

  • CAN Driver and App Layer

    Fall 2024, MAC Formula Electric FSAE


    An interrupt driven, thread-safe CAN system using modern C++.

    Read more

  • Platform Abstracted Peripherals

    2023 - 2024, MAC Formula Electric FSAE


    Implemented a common interface for peripherals to enable cross-platform execution and testing.

    Read more

  • Cross Platform Build System

    2023, MAC Formula Electric FSAE


    Compiles firmware for various platforms from a single build system.

    Read more

  • Firmware Documentation Website

    2024 - Present, MAC Formula Electric FSAE


    Comprehensive and beautiful documentation for my firmware team. Written in Markdown.

    Read more

  • Coding Playground

    2024 - Present, Personal Project


    Small projects to prototype ideas and practice new languages.

    Read more

  • Painter's Grip Pro

    Spring 2021, Top First Year Engineering Project


    A smart assistive paintbrush which detects hand tremors to avoid fibromyalgia flareups.

    Read more

  • All of the Lights

    DeltaHacks 8 (2022), Best Hardware Hack


    Controlled RGB LED strips with MOSFETs & PWM using UART commands from a Raspberry Pi web server.

    Read more

  • Sumobot

    McMaster Sumobot 2021, Best Hardware Design


    Integrated sensors & motors with an Arduino inside a 3D printed chassis.

    Read more

  • Serial Communication with LED

    May 2021, Personal Project


    Sending data between Arduinos with an LED and photoresistor.

    Read more

  • FET H-Bridge PCB

    Spring 2019, Personal Project


    Designed an H-Bridge circuit for a DC motor. Used toner-transfer and acid-etching to make my PCB.

    Read more

3D Printing Arduino Bash C++ CMake Electronics GitHub Markdown Python Raspberry Pi Rust STM32


asp Assembler

March 2025, Personal Project

Skills: Rust

See the asp repo.

Back to top


CAN Driver and App Layer

Fall 2024, MAC Formula Electric FSAE

Skills: C++, STM32

GitHub PR https://github.com/macformula/racecar/pull/339

Outcomes

  • Uses interrupts to receive messages instead of relying on polling.

    No incoming messages are ever missed. Reduces stack memory by handling less data, more often.

  • Represents missing and consumed data with C++ std::optional rather than "null-value" conventions.

    Prevents the misuse of invalid data. Inspired by Rust's Option type.

  • Message reception and processing are thread and interrupt safe by using atomic variables.

    The same layer can be used in hardware (stm32) and software simulation (SIL).

Interrupt CAN Bus

Interrupt CAN Bus

Interrupts vs Polling

STM32 CAN Rx Queue

The STM32 CAN peripheral has a 3-slot FIFO queue which automatically receives messages from the CAN bus. It is firmware's responsibility to empty the queue sufficiently quickly to avoid "overrun" which causes new messages to be dropped.

Prior to my development, the FIFO messages were handled by .ReadQueue() which emptied the FIFO and made the messages available to the application. This method risks losing data via FIFO overrun if the CAN bus receives messages faster than the application calls .ReadQueue().

My changes use the "Message Received" interrupt (CAN_IT_RX_FIFO0_PENDING) to send incoming messages all the way up to the App Layer object, where it is binned by ID and made available for firmware.

Technical Note

The previous system actually did use interrupts, but not to their full extent. A hardware interrupt would automatically empty the hardware FIFO into a software queue and the app would poll .ReadQueue()to bring up the messages from the SW queue.

While this technically uses interrupts, the SW queue could still be overrun, so it was as if interrupts weren't being used in the first place.

The only benefit to the SW queue was it had 20 slots compared to 3 in the HW FIFO, so this solution only delayed overrun instead of preventing it.

Message Containers with std::optional

C++17 introduced the "optional" data type which represents a value which may or may not exist. This provides an elegant and correct way to store messages on a CAN bus since at some points in time, a message may not have yet arrived.

std::optional replaces the common practice of indicating missing values with 0 or -1, or by maintaining a separate data_is_valid boolean alongside some data.

If GetXMsg() is called before an [X] message has arrived, the App layer will return std::nullopt. Without an optional value, the method would be forced to return an invalid XMsg object which could be misused by the application.

In this example, we determine if the FSAE battery is in the "open" state from a CAN message. If no message has arrived, we should return false. Compare the code before and after my update.

Before std::optional
bool IsContactorsOpen() {
    ContactorStates msg;
    can.Read(msg);

    return msg.timestamp != 0 &&                
           msg.pack_positive == State::OPEN &&
           msg.pack_negative == State::OPEN &&
           msg.pack_precharge == State::OPEN;
}
With std::optional
bool IsContactorsOpen() {
    auto msg = can.GetRxContactorStates();

    if(msg.has_value()) {
        return msg->PackNegative() == State::OPEN &&
               msg->PackPositive() == State::OPEN &&
               msg->PackPrecharge() == State::OPEN;
    } else {
        return false;
    }
}

Notice how much more explicit the std::optional solution is. Instead of representing message validity with a convention on the .timestamp field, the new solution requires the developer to explicity indicate the desired behaviour.

Memory Safety

What happens if firmware is reading a message via GetYMsg(), but the interrupt pushes a new Y message to the App layer? This could corrupt the data mid-read. A single container will not support synchronous reading and writing.

I wrote a SPSC buffer with two containers "left" and "right." The producer (interrupt) always writes to the opposite side from where the consumer (firmware) is reading, preventing data corruption.

This is synchronized with an atomic flag, providing a cross-platform solution. After the producer finishes adding a new message, it flips the flag, directing the next reader to consume the newly written data but without interrupting any existing read operations.

Mutexes are not an option on bare metal microcontrollers, and even if they were, it's bad practice to block a interrupt. Also, if this system is to be cross-platform, it cannot rely on interrupts since this would have no effect on our software-in-the-loop Linux server (see Platform Abstracted Peripherals).

Back to top


Platform Abstracted Peripherals

2023 - 2024, MAC Formula Electric FSAE

Skills: C++, STM32

Peripheral interface definitions https://github.com/macformula/racecar/tree/main/firmware/shared/periph

Platform peripheral drivers (ex stm32/periph) https://github.com/macformula/racecar/tree/main/firmware/mcal/

Full architecture description https://macformula.github.io/racecar/firmware/architecture/

I started writing stm32 firmware for MAC Formula Electric while on co-op and quickly recognized the need to test code without access to the physical vehicle. This inspried me to design a peripheral interface system that is abstracted from any specific platform, so it can be tested on any platform.

Outcomes

  • App-level code is completely isolated from any platform (there are no HAL commands at the app level).
  • Before testing on the vehicle, we can debug logic:
    • In the command line using the cli platforms (I/O through stdin and stdout).
    • On our automated SIL test server (I/O through server GET / PUT).
  • Old projects can be ported to new platforms almost trivially.

My procedure

  1. Wrote C++ drivers for the first two platforms' peripherals (ADC, PWM, GPIO)
    • STM32 peripherals using the STM HAL.
    • Command Line Interface drivers using stdin / stdout
  2. Extracted the common functions to create the interface.

    • Ex. A DigitalInput peripheral should have a method bool Read(void).
  3. Implemented the peripheral interfaces using ploymorphism.

C++ provides two mechanisms for polymorphism:

  • Object oriented inheritance (runtime polymprohism using the virtual table)
  • C++20 concepts (compile-time polymorphism using templates)

I really wanted to use concepts because of their novelty and compile-time determinism. I even compared the assembly code which showed the huge overhead of OOP's virtual table.

Object Oriented Inheritance
class DigitalInput {
public:
    virtual bool Read() = 0;
};

class stm::DigitalInput {
public:
    bool Read() override {
        return PLACEHOLDER_PORT & (1 << 4);
    }
};
Concepts
#include <concepts>

template <typename T>
concept DigitalInput = requires(T obj) {
    { obj.Read() } -> std::same_as<bool>;
};

class stm::DigitalInput {
public:
    bool Read() { // no override - there is no parent class
        return PLACEHOLDER_PORT & (1 << 4);
    }
};

The corresponding assembly has 9 overhead instructions (highlighted) to resolve the vtable.

Assembly code for OOP
_Z3Readv:
    push    {r4, lr}    ; push registers to stack
    ldr r3, .L6         ; load from L6 - holds LA0
    ldr r0, [r3]        ; load from LA0 - hold addr stm::DigitalInput + 8
    ldr r3, [40]        ; load from DigitalInput + 8
                        ;   holds address of address of stm::DigitalInput::Read
                        ;   function (via virtual function table)
    ldr r3, [r3]        ; load address of stm::DigitalInput::Read
    mov lr, pc          ; save program counter to link register
    bx r3               ; branch to stm::DigitalInput::Read

    _ZN4stm12DitialInput4ReadEV:    ; pasted here for visualization
        ldr r3, .L2     ; load address of IO port
        ldr r0, [r3]    ; load value of IO port
        lsr r0, r0, #4  ; right shift 4 times
        and r0, r0, #1  ; mask with 0x01
        bx lr           ; return to Read

    pop {r4, l4}        ; restore stack
    bx lr               ; return

With concepts, polymorphism is resolved at compile time, so the stm::Read() function can be executed directly with no overhead!

Assembly Code for Concepts
_Z3Readv:
    ldr r3, .L2     ; load address of IO port
    ldr r0, [r3]    ; load value of IO port
    lsr r0, r0, #4  ; right shift 4 times
    and r0, r0, #1  ; mask with 0x01
    bx lr           ; return

I started using concepts for the peripheral interfaces, but its reliance on the template type system meant that every class or function using a peripheral also had to be templated. This made it difficult to write and understand.

Ultimately, using OOP inheritance helped me develop very quickly, so I sacrificed the runtime performace to meet other development deadlines. I am still looking at methods to achieve compile time polymorphism.

Back to top


Cross Platform Build System

2023, MAC Formula Electric FSAE

Skills: CMake, Bash, Python

When I joined MAC Formula Electric, each ECU (electronic control unit) had its own firmware repository, with its own build configuration, documentation, utility functions, and IDE setup. There was a high barrier to entry for development and no way to share code between projects.

I moved all of these repositories to a monorepo, normalized their folders structures, wrote shared libraries, and create a CMake build system to easily compile any project.

Outcomes

  • Simplicity: All ECU firmware projects can be compiled with a single command, regardless of platform:

    make PROJECT=<project-name> PLATFORM=<platform-name>
    

    This produces a binary file when PLATFORM=stm32, an executable when PLATFORM=cli, etc.

    The Makefile starts the CMake system and provides other utilities like build cleaning.

  • Shared Libraries: We can share C++ libaries between projects, reducing code duplication and improving development speed.

  • IDE Flexibility: We are no longer tied to any specific IDE (like STM32CubeIDE). This means we can use open source editors (like VSCode) and extensions to enhance our development.
  • Extensible: We can easily add more features to the build system. For example, we autogenerate C++ code for our CAN messages during the build process.

Back to top


Firmware Documentation Website

2024 - Present, MAC Formula Electric FSAE

Skills: Markdown, GitHub, Python

See the live website! https://macformula.github.io/racecar/

There was very little documentation when I started leading the firmware & software teams at MAC Formula Electric. I created a documentation site to address the need for a common knowledge resource.

The content is written in Markdown using the Material for MkDocs framework. I chose Markdown to eliminate the learning curve and let me focus on writing content rather than code. Some repetitive content (like the glossary) is generated using Python & Jinja.

The site is hosted through GitHub pages and is connected to our racecar/ repository.

My contributions

Outcomes

Back to top


Coding Playground

2024 - Present, Personal Project

Skills: Rust, C++

Link to repository: https://github.com/BlakeFreer/Playground

I created the Playground repo to help me prototype, practice, and share small coding projects in different languages.

Some of my favourite projects are:

World's Worst Super Mario Game

https://github.com/BlakeFreer/Playground/tree/main/rust/mario_webserver

Mario's health is encoded in the Rust type system. A TCP web server is used to allow Mario to collect items and be damaged.

The purpose of this project was to familiarize myself with Rust's enums, match statement, and error handling while learning about TCP servers.

Mario Webserver
The graphics are a little lacking... but that wasn't the goal of this project.

Control Theory Library

https://github.com/BlakeFreer/Playground/tree/main/cpp/control

Implementations of control theory algorithms (like the Kalman filter and Linear Least Squares) as well as example code. I am currently studying "Predictive and Intelligent Control" MECHTRON 4AX3 and this project gives me a way to visualize and verify my understanding.

Uses the C++ Eigen linear algebra library and a CMake build system.

Kalman Filter
Kalman Filter estimation of vehicle position from noisy measurements.

Back to top


Painter's Grip Pro

Spring 2021, Top First Year Engineering Project

Skills: C++, Arduino, Python, 3D Printing

A smart assistive paintbrush to alert our client of an oncoming fibromyalgia muscle flareup. Detects hand tremors using an accelerometer. Provides visual and auditory feedback when tremors are detected.

My contributions

  • Designed and wired the electrical system.
  • Programmed an ATmega32 Arduino in C++ to read accelerometer data over I2C, stream data over Bluetooth, and use GPIO to alert the user.
  • CAD (Autodesk Inventor) for the paintbrush enclosure and 3D printing it.

Outcome

Selected as one of the Top 3 projects in our first year engineering course ENGINEER 1P13.

McMaster Press Release: https://www.eng.mcmaster.ca/news/first-year-engineering-students-and-budding-designers-showcase-projects-at-year-end-event/

Video Demonstration

Electrical schematic
Left to right: Power supply, Arduino controller, LED & Buzzer IO Control, 9250 Accelerometer and HC06 Bluetooth module.

Electrical assembly
Left: Arduino, IMU, power circuit. Right: Bluetooth module, LED output, buzzer.

CAD

CAD
Wide grip handle with interchanging brushes.

Back to top


All of the Lights

DeltaHacks 8 (2022), Best Hardware Hack

Skills: C++, Arduino, Raspberry Pi, Electronics

For a more in depth description, see the Devpost Link https://devpost.com/software/all-of-the-lights-b31saz

My Contributions

  • Built an LED driver using MOSFETs and a step-up logic converter to send a 3.3V UART message to a 5V device.
  • Programmed an ATtiny84 in C++ to receive colour sequences over UART and interpolate between colours with PWM signals.

Outcome

Awarded the Best Hardware Hack at DeltaHacks 8.

All of the lights schematic
Left to right: RGB Connections, MOSFETs & pulldowns, ATtiny84, double inverter step-up circuit.

All of the lights diagram

Video Demonstration

Back to top


Sumobot

McMaster Sumobot 2021, Best Hardware Design

Skills: C++, Arduino, 3D Printing, Electronics

I joined McMaster Sumobot as way to challenge myself and get involved in the McMaster Engineering community. With my first year of university being online, Sumobots allowed met to get hands-on engineering experience from my own home.

Assembly & Strategy

I assembled the circuit inside my enclosure. I used heatshrink to bundle wires in order to fit everything inside.

My strategy was to search for objects (i.e. enemy robots) and drive straight at them. I used the fact that the forward-facing ultrasonic sensors had different viewing regions. If both sensors detected an object (purple), the robot drove straight. If the object was only visible by one sensor (red or blue), the robot turned in that direction until it was straight ahead. This simple strategy was fairly effective at following moving targets.

If no targets were visible, the robot drove straight until the light sensors detected the edge of the arena. It would turn around and continue searching.

Complete assembly

Robot Strategy

Outcome

Awarded best hardware design in the 2021 McMaster Sumobot Junior competition.

Chassis

After researching Sumobot designs, I decided to stick with the tried and true plow design. I wanted rear wheel drive, as well as ultrasonic sensors to track the opposing robot.

At this point, I had not considered the interior layout of the robot. I quickly realized that the maximum 10 x 10cm footprint is VERY small and I would have to get creative with my space usage.

Initial chassis

This is a top-down layout sketch of the interior components of the robot (right edge is the front). Even when only the 3 ultrasonic sensors and 2 motor-wheel assemblies are included, it is already very crowded. ​ The front 2 ultrasonic sensors are recessed from the front edge because of the inwards-sloping ramp.

Since this is a 3D problem, I decided to begin using Autodesk Inventor.

Chassis top view

Design 1

For my first design, I focused on being compact (this was a mistake).

I placed the 2 battery packs on the floor at the very front and bottom to weigh down the front edge. The batteries would be inserted from the side.

The 3 ultrasonic sensors are placed just above the batteries, and the Arduino sits horizontally in the middle.

This design has several issues:

  • I planned on 3D printing my chassis, and this would make it difficult to print the roofs above the batteries.

  • I forgot to include space for the breadboard and motor drivers.

  • The shell profile (purple curve in the back) is nearly a cube and has a very small ramp, which is the most important offensive characteristic of the chassis design.

First chassis design

Design 2

After having many troubles with space management in my first design, I restarted and took a different approach. Instead of placing all of the components and trying to fit a shell around it, I designed out the shell first and fit the components into it. Surprisingly, this greatly helped me understand the space involved.

Like the previous design, this one has wheels at the rear and the 9V battery at the front, under the ramp where no other components fit.

Notice the 9V battery and QRD light sensor underneath the ramp. After placing the motors and 9V, I realized that nearly all of the floor space was occupied. This led me to use the vertical space.

The Arduino and Breadboard are located in the middle of the robot. They slide down into channels to hold them upright. They were raised above the floor to allow wires to pass underneath.

Second chassis design

The back of the robot is very busy, even with only the motors placed in. I was trying to find space for the 4xAA battery pack when I noticed the empty space above the motors and behind the Arduino.

​I built a shelf to support the 4xAA pack and the rear ultrasonic sensor. Since a 3D printer would not be able to make such a large bridge, this part will be printed separately.

Chassis 2 back

I was very satisfied with my design and decided to proceed with it.

After the 3D printer had been running for 6 hours, I noticed that the 9V battery was completely trapped by inner structure of the chassis. I hoped it could fit through the space beneath the ultrasonic sensors (outlined in orange) but the gap was far too tight.

I reluctantly cancelled the print and went back to the drawing board.

Chassis 2 front

9V Battery Holder

To continue using the space under the ramp for the battery, I needed to insert it from a different direction. I chose to insert it through the floor. This means that the battery has to be held against gravity. I experimented with various compliant clip designs and found these to be promising.

Notice the black clip left of the "E" in Energizer, and the pocket left of the clip that provides clearance for the clip to bend.

I test printed the battery holder on its own to save filament, but the exact same clip is embedded into the chassis (right).

Battery holder test print

Battery holder integrated in the chassis

Electrical

The 9V provides power to the Arduino and sensors. Despite having a greater voltage, it is not well suited to providing the high current demanded by the motors.

The 4xAA pack provides 6V to the motors at the higher current.

There are 6 total sensors on the robot:

  • Two ultrasonic sensors facing forwards to enable primitive object tracking.
  • One ultrasonic sensor on the rear to avoid sneak-attacks.
  • 2 QRD light sensors to detect the arena boundary.
  • One momentary button to start the program (not shown)

Sumobot schematic RED - Positive | BLACK - Negative | GREEN - Digital Output | BLUE - Digital PWM Output | ORANGE - Digital Input | YELLOW - Analog Input

Back to top


Serial Communication with LED

May 2021, Personal Project

Skills: Electronics, Arduino, C++

Sending data between Arduinos with an LED and photoresistor.

The LED flashes on and off, causing a voltage signal on the photoresistor. This can be used to transmit data. I achieved perfect transmission at a baud rate of 333 (three milliseconds per bit), above which the photoresistor response time caused errors.

I learned a lot about serial communication and the importance of timing.

See the full project at https://github.com/BlakeFreer/LED-Serial.

LED Serial setup

Back to top


FET H-Bridge PCB

Spring 2019, Personal Project

Skills: Electronics, Arduino

My grade 11 shop teacher challenged me to create an H-Bridge motor driver for a "Useless Machine." I had not yet learned about transistors so this was a difficult challenge that took me a couple months and numerous prototypes.

I also built an off-board Arduino, i.e. an ATmega328 with an external oscillator and power supply.

  1. Both PCBs were designed in AutoCAD.
  2. I printed the mirrored designs with a laser printer on inkjet paper.
  3. Used the toner-transfer method to cover my traces on a bare copper board.
  4. Finally, I soaked the boards in acid to remove the unwanted copper and soldered the components.

H-Bridge PCB

H-Bridge Board H-Bridge Board Back

ATmega328 PCB

ATmega Board ATmega Board Back

Design Process

My shop didn't have any FETs when I started the project, so I began prototyping with BJTs, knowing that the circuit topology would be similar either way.

An H-Bridge works by sending current through a motor in either direction. The FWD and BWD inputs activate either pair of diagonally-opposed transistors.

BJT H-Bridge

I initially tried using NPN transistors everywhere, but had issues activating the high side. It wasn't until I studied microelectronics in university that I learned why: the current through an NPN transistor is proportional to the Base-Emitter current, and the B-E current is caused by a voltage gradient between the base and emitter. When used on the high side, the emitter was connected to the high side of the load (i.e. the motor). This voltage is quite close to the supply voltage, so my input signal could not exceed it enough to activate the transistor.

Switching to a PNP transistor avoids this problem, but it must be activated by a low voltage, so I added an NPN inverter to flip the input signal. I learned that I would also need P-type FETs for my real H-Bridge, so I added them to the order.

Once the FETs arrived, I sketched their pinout and made various prototype circuits to learn their behaviour. I learned about pullup and pulldown resistors to prevent floating gates. I combined a N-channel low side driver and P-channel high side driver to make a half H-bridge, then added a second to complete the full H-Bridge. I still used NPN transistors to invert the gate voltage on the P-channel transistors.

FET Half Bridges

I added flyback diodes to the final circuit to protect against voltage spikes when the motor started and stopped, as well as a large capacitor to stabilize the voltage supply. With the circuit complete, I routed the traces in AutoCAD and used fabricated the PCB as described earlier.

Full H-Bridge Schematic

H-Bridge PCB Stencil

The template is mirrored so that it appears correctly after the toner-transfer.

Back to top


Extra Project: This portfolio site!

Thanks for checking out my projects! I hope you enjoyed reading them as much as I enjoyed working on them.

Since you made it this far, you may be interested in how I made this website. I chose Material for MkDocs as my framework. I'm an embedded developer, not a web developer, so I don't care to write a complex website. I'd rather be able to quickly add a new project or article without fighting syntax.

In the spirit of making the development easier, I abstracted out the project details from the this page's Markdown file. I used a json file to describe each project and used Jinja templating to create all of the cards at the top of the page.

JSON Representation
{
    "name": "FET H-Bridge PCB",
    "description": "Designed an H-Bridge circuit for a DC motor. Used toner-transfer and acid-etching to make my PCB.",
    "icon": ":material-electric-switch:",
    "date": "Spring 2019",
    "note": "Personal Project",
    "skills": [
        "Electronics",
        "Arduino"
    ],
    "writeup": "h_bridge.md"
}
  • FET H-Bridge PCB

    Spring 2019, Personal Project


    Designed an H-Bridge circuit for a DC motor. Used toner-transfer and acid-etching to make my PCB.

    Read more

The json file has one of these entries for each project, as well as a mapping between skills and icons. With all of the project details abstracted out, I can easily change the formatting for all cards simultaneously, and adding a new project is as simple as filling out a new json block.