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
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;
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));
}
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:
-
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.
-
Branch naming: Use descriptive branch names
feature/add-dsp-support
bugfix/fix-ibus-timeout
refactor/cleanup-uart-driver
-
Pull requests: Include a clear description of changes and testing performed