Skip to main content
This guide outlines the coding standards and style conventions used in the BlueBus firmware codebase. Following these guidelines ensures consistency and maintainability.

File organization

The BlueBus firmware follows a modular architecture with clear separation of concerns:

Directory structure

firmware/application/
├── lib/              # Core libraries and drivers
│   ├── bt/          # Bluetooth module drivers (BM83, BC127)
│   ├── ibus.c/h     # I-Bus protocol implementation
│   ├── uart.c/h     # UART communication
│   ├── i2c.c/h      # I2C bus driver
│   ├── timer.c/h    # Timer and scheduling
│   ├── config.c/h   # EEPROM configuration
│   └── utils.c/h    # Utility functions
├── handler/         # Event handlers
│   ├── handler_bt.c/h     # Bluetooth event handling
│   ├── handler_ibus.c/h   # I-Bus event handling
│   └── handler_common.c/h # Shared handler logic
├── ui/              # User interface implementations
│   ├── bmbt.c/h     # Board Monitor interface
│   ├── mid.c/h      # Multi-Information Display
│   ├── cd53.c/h     # CD53 radio interface
│   └── cli.c/h      # Command-line interface
├── main.c           # Application entry point
├── sysconfig.h      # System configuration pragmas
└── mappings.h       # Pin and hardware mappings

File headers

Every source file must begin with a standard header comment:
/*
 * File: filename.c
 * Author: Your Name <[email protected]>
 * Description:
 *     Brief description of the file's purpose
 */

Naming conventions

Functions

Function names use PascalCase with a module prefix:
// Good
void IBusInit(void);
uint8_t ConfigGetBootloaderMode(void);
void TimerDelayMicroseconds(uint32_t microseconds);

// Bad
void ibus_init(void);      // Wrong: snake_case
void initIBus(void);       // Wrong: no module prefix
void Init(void);           // Wrong: too generic

Variables

Local variables use camelCase:
uint8_t boardVersion = UtilsGetBoardVersion();
char *deviceName = "BlueBus";
int32_t sleepCount = 0;
Global or module-level variables may use PascalCase:
static int8_t BOARD_VERSION = -1;

Constants and macros

Constants and macros use UPPER_SNAKE_CASE with a module prefix:
#define IBUS_DEVICE_CDC 0x18
#define IBUS_DEVICE_RAD 0x68
#define IBUS_PKT_SRC 0
#define IBUS_PKT_DST 2

#define CONFIG_SN_ADDRESS_MSB 0x00
#define CONFIG_FIRMWARE_VERSION_MAJOR_ADDRESS 0x02

#define UART_BAUD_115200 115200
#define UART_PARITY_NONE 0

Struct and type names

Types use PascalCase with a _t suffix:
typedef struct {
    struct UART_t uart;
    uint8_t status;
    uint8_t deviceId;
} IBus_t;

typedef struct {
    char text[UTILS_DISPLAY_TEXT_SIZE];
    uint8_t index;
    uint16_t timeout;
    uint8_t status;
    uint8_t length;
} UtilsAbstractDisplayValue_t;

Code formatting

Indentation

Use 4 spaces for indentation (not tabs):
int main(void)
{
    // Set the IVT mode
    IVT_MODE = IVT_MODE_APP;

    // Initialize low level modules
    EEPROMInit();
    TimerInit();
    I2CInit();

    struct BT_t bt = BTInit();
    struct IBus_t ibus = IBusInit();

    return 0;
}

Braces

Place opening braces on a new line for functions, structs, and control statements:
// Good
void UtilsReset(void)
{
    asm("reset");
}

if (condition)
{
    doSomething();
}
else
{
    doSomethingElse();
}

// Also acceptable for simple statements
if (condition) {
    doSomething();
}

Line length

Keep lines under 80-100 characters when possible. Break long function calls across multiple lines:
// Good
struct UART_t systemUart = UARTInit(
    SYSTEM_UART_MODULE,
    SYSTEM_UART_RX_RPIN,
    SYSTEM_UART_TX_RPIN,
    SYSTEM_UART_RX_PRIORITY,
    SYSTEM_UART_TX_PRIORITY,
    UART_BAUD_115200,
    UART_PARITY_NONE
);

Whitespace

Use whitespace to improve readability:
// Good
uint8_t result = (value * 2) + offset;
if (a == b && c != d) {
    process();
}

// Bad
uint8_t result=(value*2)+offset;
if(a==b&&c!=d){
    process();
}

Documentation

Function documentation

Document all public functions with a block comment describing purpose, parameters, and return value:
/**
 * UtilsConvertCmToIn()
 *     Description:
 *         Convert Centimeters to the nearest whole inch
 *     Params:
 *         uint8_t cm - Centimeters
 *     Returns:
 *         uint8_t The converted and rounded value in inches
 */
uint8_t UtilsConvertCmToIn(uint8_t cm)
{
    float impVal = cm / 2.54;
    return (int)(impVal < 0 ? (impVal - 0.5) : (impVal + 0.5));
}

Inline comments

Use inline comments to explain complex logic or non-obvious behavior:
// Enable the pull-down on the board version pin
BOARD_VERSION_PD = 1;

// Set the UART mode to MCU for the remainder of this application code
UART_SEL = UART_SEL_MCU;

// SPDIF_RST is reused from version one where it was TEL_MUTE
// Do not alter its state on v1.x boards
if (boardVersion == BOARD_VERSION_TWO) {
    SPDIF_RST = 1;
}

Hardware abstraction

Pin mappings

Define all hardware-specific pin mappings in mappings.h:
// I/O Pin Modes
#define BT_MFB_MODE TRISFbits.TRISF4
#define BT_RST_MODE TRISFbits.TRISF5
#define IBUS_EN_MODE TRISEbits.TRISE0

// I/O Pin States
#define BT_MFB LATFbits.LATF4
#define BT_RST LATFbits.LATF5
#define IBUS_EN LATEbits.LATE0

Device-specific code

Use conditional compilation for board-specific behavior:
if (boardVersion == BOARD_VERSION_ONE) {
    WM88XXInit();
} else if (boardVersion == BOARD_VERSION_TWO) {
    SPDIF_RST = 1;
}

Compiler settings

The project uses the following XC16 compiler settings:

Optimization

  • Optimization level: 1 (moderate optimization)
  • Code model: Large code
  • Data model: Large data
  • Scalar model: Large scalar

Warnings

  • Enable all warnings: Yes
  • Enable fatal warnings: Yes
  • Cast align: Enabled
  • SFR warnings: Enabled
All code must compile without warnings. If you encounter unavoidable warnings, document them with a comment explaining why they can be safely ignored.

Error handling

Trap handlers

The BlueBus implements trap handlers for critical errors. All traps increment an EEPROM counter and perform a controlled reset:
void __attribute__ ((__interrupt__, auto_psv)) _AltOscillatorFail()
{
    // Clear the trap flag
    INTCON1bits.OSCFAIL = 0;
    ConfigSetTrapIncrement(CONFIG_TRAP_OSC);
    TrapWait();
}

Return values

Use meaningful return values for error conditions:
// Good
#define UART_STATUS_OK 0
#define UART_STATUS_ERROR 1
#define UART_STATUS_TIMEOUT 2

uint8_t UARTSendData(struct UART_t *uart, uint8_t *data, uint16_t len)
{
    if (uart == NULL || data == NULL) {
        return UART_STATUS_ERROR;
    }
    // ... send data ...
    return UART_STATUS_OK;
}

Best practices

Use standard types

Always use fixed-width integer types from <stdint.h>:
// Good
uint8_t byte;
uint16_t word;
uint32_t dword;
int8_t signedByte;

// Bad
char byte;           // Size may vary by platform
int word;            // Size may vary by platform
long dword;          // Size may vary by platform

Initialize variables

Always initialize variables before use:
// Good
uint8_t count = 0;
char *name = NULL;

// Bad
uint8_t count;
char *name;

Avoid magic numbers

Define constants for all magic numbers:
// Good
#define IBUS_DEVICE_CDC 0x18
if (device == IBUS_DEVICE_CDC) {
    // ...
}

// Bad
if (device == 0x18) {
    // ...
}

Memory management

The BlueBus firmware runs bare-metal without dynamic memory allocation. Avoid malloc() and free():
// Good - Static allocation
static char buffer[256];

// Good - Stack allocation
void function(void) {
    char localBuffer[64];
    // ...
}

// Bad - Dynamic allocation (not supported)
void function(void) {
    char *buffer = malloc(256);  // Don't do this!
    free(buffer);
}
The PIC24FJ1024GA606 has 16KB of RAM. Be mindful of stack usage, especially with large local arrays in deeply nested function calls.

Git conventions

When contributing code:
  1. Commit messages: Use clear, descriptive commit messages
    Add support for PCM5122 volume control
    
    Implement I2C communication to adjust DAC volume based on
    user settings from the vehicle display.
    
  2. Branch naming: Use descriptive branch names
    • feature/add-dsp-support
    • bugfix/fix-ibus-timeout
    • refactor/cleanup-uart-driver
  3. Pull requests: Include a clear description of changes and testing performed

Build docs developers (and LLMs) love