Skip to content

concordia-fsae/firmware

Repository files navigation

Concordia FSAE Firmware Repo

Documenting the Code

README.md should be stored in each component folder. See below for programming guidelines for this repository.

Component Folder and File Hierarchy

Components

Name Designator Path Comments
Steering Wheel stw components/steering_wheel/ Steering Wheel Controller
BMS Boss bmsb components/bms_boss/ Battery Management System Boss Controller
BMS Worker bmsw components/bms_worker/ Battery Management System Boss Controller
Bootloader bl components/bootloader/ Embedded Bootloaders for all Controllers
Heartbeat heartbeat components/heartbeat/ Generic Heartbeat Application for all Controllers
Front Vehicle Controller vcfront components/vc/front Vehicle Controller code specific to the Front Controller
Power Distribution Unit vcpdu components/vc/pdu Vehicle Controller code specific to the Power Distribution Unit
Rear Vehicle Controller vcrear components/vc/rear Vehicle Controller code specific to the Rear Controller

Platforms

Platform Designator Comments
CFR24 cfr24 Competition car for 2024
CFR25 cfr25 Competition car for 2025

Sections

  • Each component is broken into 3 main code sources
  1. Source Code
    • The source code is typically located in ./components/$COMPONENT_DESIGNATOR/ for each component
    • Hardware sources to be included ./components/$DESIGNATOR/HW/mcuConfig.yaml unless multiple platforms are supported
    • The source code, while it varies between component, has these typical areas:
      1. ./components/$COMPONENT_DESIGNATOR/build/
        • Contains all object files, binary files, etc...
      2. ./components/$COMPONENT_DESIGNATOR/generated/
        • Contains code generated by scripts and/or programs which is used by the embedded system
      3. ./components/$COMPONENT_DESIGNATOR/HW/
        • Contains all firmware source code and header files which interfaces between System Calls and the Hardware
      4. ./components/$COMPONENT_DESIGNATOR/include/
        • Contains all System and Application header files
      5. ./components/$COMPONENT_DESIGNATOR/lib/
        • Contains any library source code and header files which the System uses
      6. ./components/$COMPONENT_DESIGNATOR/RTOS -Contains all source code and header files relating to the implementation of the System's RTOS
      7. ./components/$COMPONENT_DESIGNATOR/src
        • Contains all System and Application level source code
      8. ./components/$COMPONENT_DESIGNATOR/SConscript
        • Is called by ./SConstruct to run the component build script
      9. ./components/shared/: Team libraries, configurations, drivers, firmware, and other...
  2. Embedded System
    • Contains libraries, openocd configurations, and platform code accessible to all devices located in ./embedded/
    • Select important sections:
      1. ./embedded/libs/CMSIS/: Controller independant system level library
        • Used for RTOS and other controller independant systems
      2. ./embedded/platforms/: Supported Hardware and MCU platforms
        • Contains the HAL/LL, startup code, and link script for the STM32 family of devices in ./embedded/platforms/stm32/
  3. Chip Configuration
    • The chip configuration is stored in YAML files in ./site_scons/chips.yaml and ./site_scons/components.yaml
    • ./site_scons/chips.yaml contains the configuration of the chip source files and headers to be included by the build system
    • ./site_scons/components.yaml contains the different components and their sources for the build system
    • ./site_scons/platforms.yaml contains the different platforms and their component composition

Setting Up and Using the Development Environment

Workflows

1. Installation and Set-Up

  1. Clone repository
    • Clone with ssh link. This enables ssh when pushing and pulling
  2. Pull in submodules
    • git submodule update --init --recursive
  3. Install and start docker (OS-specific)
    • Windows Users: Install bash emulator: cygwin, mingw64, WSL (test optimal tool)
  4. Install docker compose (OS-specific)
  5. Execute docker script from root directory
    • ./buildroot.sh
      • Change permissions if necessary: chmod +x buildroot.sh
      • Note: It has been noticed that a bug on windows and mac hosts creates unexpected build failures. Known workaround is to run it from a linux virtual machine.

2. Container Usage

  • All components, and their designator, are listed in ./site_scons/components.yaml
  1. Building the program(s)
    • Execute the SCons program from inside the env
      • scons --targets=$COMPONENT_DESIGNATOR build is to build single components
        • ex: To build the stw, execute scons --targets=stw build. Build is the default action for a target and therefore does not need to be specified
        • Note: build is the default target, so scons --target=bmsb is analogous to scons --target=bmsb build
        • Build targets can be seperated by a pound #
        • The config ID of a component can be specified with a colon : and can be seperated with a comma ,
        • An example of a complex build is scons --targetsbmsb:0#bmsw:1,2
      • scons --platform=$PLATFORM_DESIGNATOR is to build an entire platform
  2. Uploading the program over SWD (Optional)
    • The SCons build environment has an optional upload command that flashes the device and holds
    • To flash and verify, execute scons --targets=$COMPONENT_DESIGNATOR --upload from the env
  3. Debugging the program
    • More information and better debugging workflows can be found by researching OpenOCD and GDB commands
    • Debugging in the env is done through GDB and OpenOCD
    • To start the OpenOCD server and connect to the remote host in GDB, execute:
      • scons --targets=$COMPONENT_DESIGNATOR --debugger from the env
      • You can chain multiple commands together. For example: 'scons --target=stw upload openocd-gdb' will build, upload, and start the debugger
    • The standard GDB commands apply. To reset or halt the MCU through the SWD connection, execute:
      • monitor reset, monitor reset halt
    • It is always good when you first open the env to execute st-info --probe to see if the ST-LINK is recognized

Sections

  1. SCons Tools
    • Located in ./site_scons/site_tools/
    • Custom python scripts added to the SCons build environment
  2. Embedded Systems
    1. Toolchains
      • Downloaded from ARM Developper
      • Located in ./embedded/toolchains
    2. USB Loader/Debugger
      • Located in ./embedded/openocd
      • Interfaces and stores configurations of different chips/boards
    3. Platforms
      • Supported hardware platforms
      • Located in ./embedded/platforms/
  3. SCons related scripts
    • ./SConstruct
    • ./components/$DESIGNATOR/Sconscript

Notes

  • The script will set up the env for compiling - no need to run twice
  • Due to the mounting feature of the docker container, anytime the container is open the changes applied to the files will automatically and immediately be accessible to the SCons tool
    • This also means that, once built, the build dir will be on your local machine as well. Run scons --clean in container if you want to re-build from scratch
  • If doing loading/debugging of physical hardware, the ST-LINK or other interface must be plugged in by USB before starting the container. Docker containers do not support dynamic loading of USB peripherals
    • Note: This only works if no bootloader is installed. If a bootloader is installed and you are debugging through JTAG, you should still flash over CAN

Programming Guidelines

Internal file templates and section templates located in ./shared/templates/

Rules

  • Use good modular design. Think carefully about the program structure, functions, and data structures before starting to write the code

  • Use proper error detection and handling. Always check return values from functions and handle errors appropriately. Error_Handler() is not proper error handling for production level code (typically).

  • If the component uses dynamic memory allocation (typically, we do not support dynamic memory allocation in this framework) you must have code somewhere in the program to release memory.

  • Use descriptive names for functions and important variables. GetRadius() should be used instead of foo() for a function that returns the radius of a circle.

  • Define constants and use them, especially for configuration values. If you have a buffer and decide it should be 100 units long, then:

    Compliant

    #define BUFFER_SIZE 50U
    ...
    uint8_t buf[BUFFER_SIZE];
    

    Non Compliant

    uint8_t buf[50];
    
  • Keep each variable to the smallest scope possible. Do not have a variable global to a file if it is only used from one function. Avoid using global variables

  • Use standard types such as uint8_t, int32_t, etc... Do not use int or unsigned int.

  • Application level code should not contain system level details. System level code should not contain firmware level details. Etc... Utilize a top-down design

  • Line length should be kept relative. While we believe in monitors with unlimited width, do not have a line 200 characters long. A good rule of thumd is roughly 100 characters long, or just a bit biggetr than a section heading.

  • Documment your code, if you are doing something ambiguous, something that relates to hardware details, etc... detail the execution flow of your program. If you know what it does great, but once you are no longer on the team your knowledge will not help the next person unless you look forward to babysitting (for free)

    • Every file, function, etc... should be documented in line with the Documentation Guidelines
  • Only single assignment per line, if assigning multiple items do it over multiple lines

    Compliant

    uint32_t a, b, c;
    a = 0;
    b = a;
    c = a;
    

    Non Compliant

    uint32_t a, b, c;
    a = b = c = 0;
    
  • Use proper formatting. Formatting details can be found in .clang-format

File Sections

  • Each file is broken into sections such as Includes, Defines, etc...

  • An empty line is to follow and come before the section header

    Compliant Code

    /******************************************************************************
     *                              D E F I N E S
     ******************************************************************************/
    
    #define ADC_PRECALIBRATION_DELAY_ADCCLOCKCYCLES    2U
    #define ADC_CALIBRATION_TIMEOUT                    10U
    
    /******************************************************************************
     *                           P U B L I C  V A R S
     ******************************************************************************/
    

    Non-Compliant Code

    /******************************************************************************
     *                              D E F I N E S
     ******************************************************************************/
    #define ADC_PRECALIBRATION_DELAY_ADCCLOCKCYCLES    2U
    #define ADC_CALIBRATION_TIMEOUT                    10U
    
    /******************************************************************************
     *                           P U B L I C  V A R S
     ******************************************************************************/
    

Pre-Processor

  • Macros

    • Macros are to be capitalized

      Compliant #define NEGATE(x) -(x)

      Non Compliant #define negate(x) -(x)

    • Reference to macro variables are to be in brackets

      Compliant #define NEGATE(x) -(x)

      Non Compliant #define NEGATE(x) -x

  • Defines

    • Define names are to be capitalized and seperated by an under-score: CAPITALIZED_AND_SCORED

    • Defines must be given meaningful names. #define TOTAL_MPRL_SENSORS 8 instead of #define N_SENS 8

    • Do not use magic numbers

      Compliant

      #define USE_THIS_THING 0
      #define USE_THAT_THING 1
      #define CONFIG_SETTING USE_THIS_THING
      

      Non Compliant

      #define CONFIG_SETTING 0
      
  • Header files

    • Contents should be protected from multiple inclusion with #pragma once

      Compliant

      /* In showcase.h */
      #pragma once
      
      /* header contents */
      uint32_t func_showcase();
      

      Non Compliant

      /* In showcase.h */
      
      /* header contents */
      uint32_t func_showcase();
      

Naming Conventions

  • Each name can be broken into its MODULE, LIBRARY, and Specifics
    • Optional MODULE: To be capitalized
    • Optional LIBRARY: To be capitalized
    • Specifics: Case dependant
  1. Files

    • To be named with the module and library. Such as HW_ADC.* for the firmware of the ADC
  2. Functions

    • Names are to be given by MODULE_LIBRARY_TaskSpecifics. Such as HW_I2C_MasterTransmit to transmit data as a master. TaskSpecifics to be in PascalCase
  3. Types

    1. Structs
      • Follow the MODULE_LIBRARY_Specifics standard and be appended with _S. Specifics to be in PascalCase

        Compliant SYS_IO_S

        Non Compliant Io_s

      • Struct members should be miniscule, seperated by under-scores, and meaningful

        Compliant

        typedef struct
        {
            uint32_t pcb_temp;
            ...
        } HW_EnvValues_S;
        

        Non Compliant

        typedef struct
        {
            uint32_t a;
            ...
        } env_s;
        
    2. Enums
      • Follow the MODULE_LIBRARY_Specifics standard and be appended with _E. Specifics to be in PascalCase

        Compliant ADC_Channels_E

        Non Compliant AdcChannels

      • Members must be capitalized and seperated by underscore

        Compliant

        typedef enum
        {
            ADC_CHANNEL_CURRENT_SENSE = 0U,
            ...
        } ADC_Channels_E;
        

        Non Compliant

        typedef enum
        {
            currentSense = 0,
            ...
        } ADC_Channels_E;
        
      • Indexes are to be started with = 0U or = 0x00

  4. Variables

    1. Generic

      • Globals
        • Must be CAPITALIZED_AND_SCORED
        • Must be given meaningful name based on MODULE_LIBRARY_SPECIFICS
      • Local to module
        • Must be given meaningful name based on LIBRARY_specifics. LIBRARY to be capitalied and specifics to be miniscule
        • Library suffix is optional
      • Local to function
        • Must be miniscule_and_scored
        • Must be given meaningful name
      • Intermediates
        • Intermediates should be given a meaningful name in miniscule_and_scored. They should be used or discarded

          Compliant

          #define EINVAL 1
          
          int32_t func_showcase(uint32_t param)
          {
              int32_t error;
          
              if (param < 32U) {
                  error = 0;
              } else {
                  error = -EINVAL;
              }
          
              return error;
          }
          
          void main(uint32_t index)
          {
              int32_t error;
              uint32_t test;
              uint32_t array_showcase[32];
          
              error = func_showcase(index);
              func_showcase(index); // Also OK
          
              if (error == 0) {
                      test = array_showcase[index];
              }
          }
          

          Non Compliant

          #define EINVAL                22
          
          int32_t func_showcase(uint32_t param)
          {
              int32_t error;
          
              if (param < 32U) {
                  error = 0;
              } else {
                  error = -EINVAL;
              }
          
              return error;
          }
          
          void main(uint32_t index)
          {
              int32_t error;
              uint32_t test;
              uint32_t array_showcase[32];
          
              error = func_showcase(index);
          
              test = array_showcase[index];
          }
          
    2. Structs

      • Must be named with camelCase and appended with _S

        Compliant paddlesRaw_S

        Non Compliant paddles

    3. Enums

      • Must be named with camelCase and appended with _E

        Compliant currentState_E

        Non Compliant state

Documentation Guidelines

To be used with doxygen-style comments

  • Some detailed rules are listed below to illustrate the comments format for each function:
    • The comments block shall start with /** (slash-asterisk-asterisk) in a single line.
    • The comments block shall end with */ (space-asterisk-slash) in a single line.
    • Other than the first line and the last line, every line inside the comments block shall start with * (space-asterisk). It also applies to the line which is used to separate different paragraphs. We’ll call it a blank line for simplicity.
    • Single line comments shall be C-style // Code comment
  1. Files

    • Each file must contain a header which details the file name and a brief description at a minimum

      Compliant

      /* in header.h */
      /**
      * @file header.h
      * @brief  Details the configuration of this implementation
      */
      

      Non Compliant

      /* in header.h */
      // This code houses the configuration
      
  2. Functions

    • For each function, following information shall be documented: brief description, detailed description, parameters description, pre-conditions, post-conditions, return value description, and comments explaining the actual return values. We’ll call each block of information a paragraph for simplicity. A paragraph may be removed from the list if it is not applicable for that function.

    • Each line shall only contain the description for one parameter, or one pre-condition, or one post-condition, or one actual return value. We’ll call each of these an element for simplicity.

    • A blank line shall separate different paragraphs. Inside each paragraph, a blank line is not required to separate each element.

    • The brief description of the function shall be documented with the format @brief .

    • No specific format is required for the detailed description of the function.

    • The description of the function parameter shall be documented with the format @param .

    • The pre-condition of the function shall be documented with the format @pre .

    • The post-condition of the function shall be documented with the format @post .

    • The brief description of the function return value shall be documented with the format @return .

    • A void-returning function shall be documented with the format @return None.

    • The comments explaining the actual return values shall be documented with the format @retval .

    • If the description of one element needs to span multiple lines, each line shall be aligned to the start of the description in the first line for that element.

    • The comments block shall appear immediately before the function definition/declaration in the C source file or header file.

      Compliant

      /**
       * @brief Brief description of the function.
       *
       * Detailed description of the function. Detailed description of the function. Detailed description of the
       * function. Detailed description of the function.
       * Application Constraints: Detailed description of application constraint.
       *
       * @param param_1 Parameter description for param_1.
       * @param param_2 Parameter description for param_2.
       * @param param_3 Parameter description for param_3. Parameter description for param_3. Parameter description
       *                for param_3. Parameter description for param_3. Parameter description for param_3. Parameter
       *                description for param_3.
       *
       * @pre param_1 != NULL
       * @pre param_2 <= 255U
       *
       * @post retval <= 0
       *
       * @return Brief description of the return value.
       *
       * @retval 0 Success to handle specific case.
       * @retval -EINVAL Fail to handle specific case because the argument is invalid.
       * @retval -EBUSY Fail to handle specific case because the target is busy.
       *
       */
      int32_t func_showcase(uint32_t *param_1, uint32_t param_2, uint32_t param_3);
      

      Non Compliant

      /* Brief description of the function.
      Detailed description of the function. Detailed description of the function. Detailed description of the
      function. Detailed description of the function.
      
      @param param_1 Parameter description for param_1. @param param_2 Parameter description for param_2.
      @param param_3 Parameter description for param_3. Parameter description for param_3. Parameter description
      for param_3. Parameter description for param_3. Parameter description for param_3. Parameter
      description for param_3.
      
      pre-conditions: param_1 != NULL, param_2 <= 255U
      post-conditions: retval <= 0
      
      Brief description of the return value. */
      int32_t func_showcase(uint32_t *param_1, uint32_t param_2, uint32_t param_3);
      
  3. Types

    • Types are to be documented on the previous line of the definition and be enclosed in a doxygen-style block

      Compliant

      // @brief Documentation of the type
      typedef uint32_t Color;
      

      Non Compliant typedef uint32_t Color; // Documenting

  4. Variables

    • Types are to be documented on the same line as the definition and be enclosed in a doxygen-style block

      Compliant uint32_t addr; // @brief Documentation of the type

      Non Compliant uint32_t a; // Documenting

Releases

No releases published

Packages

No packages published