Skip to main content
BlueBus supports two different Bluetooth modules through a unified abstraction layer. The choice of module depends on the hardware version:
  • BC127 (Sierra Wireless) - Hardware v1.x
  • BM83 (Microchip) - Hardware v2.x

Module abstraction

The bt.c file provides a hardware-agnostic API that automatically routes calls to the appropriate module:
void BTCommandPlay(BT_t *bt)
{
    if (bt->type == BT_BTM_TYPE_BC127) {
        BC127CommandPlay(bt);
    } else {
        BM83CommandMusicControl(bt, BM83_CMD_ACTION_PLAY);
    }
}
This abstraction allows the rest of the codebase to work identically regardless of which Bluetooth module is installed.

Module detection

The module type is determined by reading the board version pin during initialization:
BT_t BTInit()
{
    BT_t bt;
    bt.type = UtilsGetBoardVersion();
    // Hardware v1 = BC127, Hardware v2 = BM83
}

BC127 implementation

The BC127 is a text-based AT command module from Sierra Wireless.

Communication protocol

The BC127 uses ASCII commands terminated with carriage return (0x0D):
#define BC127_MSG_END_CHAR 0x0D
#define BC127_MSG_DELIMETER 0x20  // Space character

void BC127SendCommand(BT_t *bt, char *command)
{
    uint8_t length = strlen(command);
    CharQueueWrite(&bt->uart.txQueue, (uint8_t *) command, length);
    CharQueueWrite(&bt->uart.txQueue, (uint8_t *) "\r", 1);
}

Common commands

The BC127 implements numerous AT-style commands:
// Open A2DP profile to device
OPEN {mac_address} A2DP

// Close specific connection
CLOSE {link_id}

// List paired devices
LIST

// Set discoverable/connectable state
BT_STATE {connectable} {discoverable}

Event handling

The BC127 sends unsolicited event messages:
void BC127Process(BT_t *bt)
{
    while (CharQueueGetSize(&bt->uart.rxQueue) > 0) {
        char c = CharQueueNext(&bt->uart.rxQueue);
        if (c == BC127_MSG_END_CHAR) {
            // Parse complete message
            if (strncmp(message, "OPEN_OK", 7) == 0) {
                BC127ProcessEventOpenOk(bt, tokens);
            } else if (strncmp(message, "AVRCP_PLAY", 10) == 0) {
                BC127ProcessEventAVRCPPlay(bt, tokens);
            }
        }
    }
}
Key events include:
  • OPEN_OK: Profile connection established
  • CLOSE_OK: Connection closed
  • AVRCP_PLAY: Playback started
  • AVRCP_PAUSE: Playback paused
  • CALL_ACTIVE: Active call
  • METADATA: Track information received

Profile management

The BC127 tracks up to 9 active profiles per device:
typedef struct BTConnection_t {
    uint8_t deviceId;  // Device index
    uint8_t linkId;    // Link ID for this profile
    uint8_t profiles[BT_PROFILE_COUNT];
} BTConnection_t;
Profiles include:
  • A2DP (Audio streaming)
  • AVRCP (Remote control)
  • HFP (Hands-free)
  • PBAP (Phonebook access)
  • MAP (Message access)

BM83 implementation

The BM83 uses a binary packet protocol instead of text commands.

Packet structure

BM83 packets have a fixed header structure:
[0xAA] [Length MSB] [Length LSB] [Command] [Data...] [Checksum]
  • Start byte: Always 0xAA
  • Length: 16-bit big-endian length of data + command
  • Command: Single-byte command code
  • Data: Variable length payload (max 636 bytes)
  • Checksum: Sum of all bytes except start byte and checksum itself

Command examples

void BM83CommandMusicControl(BT_t *bt, uint8_t action)
{
    uint8_t packet[] = {
        0x00,  // Database index
        action // Action code
    };
    BM83SendCommand(bt, BM83_CMD_MUSIC_CONTROL, packet, 2);
}

// Actions:
// BM83_CMD_ACTION_PLAY 0x05
// BM83_CMD_ACTION_PAUSE 0x06
// BM83_CMD_ACTION_NEXT 0x09
// BM83_CMD_ACTION_PREVIOUS 0x0A

Event processing

The BM83 sends binary event packets:
void BM83Process(BT_t *bt)
{
    // Parse packet header
    if (startByte == BM83_UART_START_WORD) {
        uint16_t length = (rxQueue[1] << 8) | rxQueue[2];
        uint8_t command = rxQueue[3];
        
        switch (command) {
            case BM83_EVT_BTM_STATUS:
                BM83ProcessEventBTMStatus(bt, data, length);
                break;
            case BM83_EVT_CALL_STATUS:
                BM83ProcessEventCallStatus(bt, data, length);
                break;
            case BM83_EVT_AVC_SPECIFIC_RSP:
                BM83ProcessEventAVCSpecificRsp(bt, data, length);
                break;
        }
    }
}

AVRCP metadata

The BM83 retrieves metadata through AVRCP vendor-specific commands:
void BM83CommandAVRCPGetElementAttributesAll(BT_t *bt)
{
    uint8_t packet[] = {
        BM83_AVRCP_PDU_GET_ELEMENT_ATTRIBUTES,
        0x00  // Get all attributes
    };
    BM83SendCommand(bt, BM83_CMD_AVC_VENDOR_DEPENDENT_CMD, packet, 2);
}
Metadata attributes:
  • 0x01 - Title
  • 0x02 - Artist
  • 0x03 - Album
  • 0x04 - Track number
  • 0x05 - Total tracks
  • 0x06 - Genre
  • 0x07 - Playing time

Shared structures

Both modules share common data structures defined in bt_common.h:

Bluetooth context

typedef struct BT_t {
    UART_t uart;
    uint8_t type;              // BC127 or BM83
    uint8_t status;            // Connection status
    uint8_t connectable;       // Connectable state
    uint8_t discoverable;      // Discoverable/pairing state
    uint8_t callStatus;        // Call state
    uint8_t playbackStatus;    // Playing/paused
    char title[BT_METADATA_FIELD_SIZE];
    char artist[BT_METADATA_FIELD_SIZE];
    char album[BT_METADATA_FIELD_SIZE];
    char callerId[BT_CALLER_ID_FIELD_SIZE];
    BTPairedDevice_t pairedDevices[BT_MAX_PAIRINGS];
    BTActiveDevice_t activeDevice;
    BTPBAP_t pbap;
} BT_t;

Paired device

typedef struct BTPairedDevice_t {
    uint8_t macId[BT_LEN_MAC_ID];
    char deviceName[BT_DEVICE_NAME_LEN];
    uint8_t number;  // Index in pairing list
} BTPairedDevice_t;

Active device

typedef struct BTActiveDevice_t {
    uint8_t deviceIndex;  // Index in pairedDevices array
    uint8_t aclId;        // ACL connection ID
    uint8_t a2dpId;       // A2DP link ID
    uint8_t avrcpId;      // AVRCP link ID
    uint8_t hfpId;        // HFP link ID
    uint8_t pbapId;       // PBAP link ID
} BTActiveDevice_t;
The active device structure tracks individual profile connection IDs, allowing the system to manage multiple simultaneous profiles with a single device.

PBAP integration

Both modules support Phone Book Access Profile (PBAP) for contact management.

PBAP session

typedef struct BTPBAP_t {
    uint8_t active: 1;           // Session active
    uint8_t status: 2;           // Idle/pending/waiting
    uint8_t contactCount;        // Contacts in buffer
    uint8_t contactIdx;          // Current contact index
    BTPBAPContact_t contacts[BT_PBAP_MAX_CONTACTS];
    BTPBAPParserState_t parser;  // vCard parser state
} BTPBAP_t;

Contact structure

typedef struct BTPBAPContact_t {
    char name[BT_PBAP_CONTACT_NAME_LEN];
    BTPBAPContactTelephone_t numbers[BT_PBAP_CONTACT_MAX_NUMBERS];
    uint8_t numberCount;
} BTPBAPContact_t;

typedef struct BTPBAPContactTelephone_t {
    uint8_t number[8];  // BCD encoded
    uint8_t type;       // Cell/home/work
} BTPBAPContactTelephone_t;

Phonebook types

BlueBus can access multiple phonebooks:
#define BT_PBAP_OBJ_PHONEBOOK 0x00  // Main contacts
#define BT_PBAP_OBJ_INCOMING 0x01   // Incoming calls
#define BT_PBAP_OBJ_OUTGOING 0x02   // Outgoing calls
#define BT_PBAP_OBJ_MISSED 0x03     // Missed calls
#define BT_PBAP_OBJ_COMBINED 0x04   // All calls
#define BT_PBAP_OBJ_FAVORITES 0x05  // Favorites
#define BT_PBAP_OBJ_SPEEDDIAL 0x06  // Speed dial

vCard parsing

Contacts are received in vCard 2.1 or 3.0 format:
BEGIN:VCARD
VERSION:2.1
N:Doe;John;;;
TEL;CELL:+1234567890
TEL;HOME:+0987654321
END:VCARD
The parser (BTPBAPParserState_t) handles:
  • Multi-line vCard entries
  • Fragmented packet reassembly
  • Multiple phone numbers per contact
  • Number type detection (cell/home/work)

Call management

Both modules support hands-free calling:

Call states

#define BT_CALL_INACTIVE 0
#define BT_CALL_ACTIVE 1
#define BT_CALL_VR 2        // Voice recognition
#define BT_CALL_INCOMING 3
#define BT_CALL_OUTGOING 4

SCO audio

SCO (Synchronous Connection-Oriented) links carry voice audio:
#define BT_CALL_SCO_CLOSE 5
#define BT_CALL_SCO_OPEN 6
When SCO opens, BlueBus:
  1. Switches audio routing to Bluetooth
  2. Enables microphone input
  3. Adjusts gain levels
  4. Displays call information on vehicle displays

Caller ID

Caller ID information is stored and displayed:
char callerId[BT_CALLER_ID_FIELD_SIZE];

// For incoming calls, caller ID from phone
// For outgoing calls, dialed number
// For voice recognition, localized "Voice Assistant" string

Audio configuration

BlueBus configures Bluetooth audio output differently per module:

BC127 audio

The BC127 supports both analog and S/PDIF output:
void BC127CommandSetAudioDigital(BT_t *bt, 
    char *sampleRate,  // "48000"
    char *bits,        // "16" or "24" 
    char *mode,        // "0" = stereo
    char *route        // "2" = S/PDIF
);

BM83 audio

The BM83 outputs I2S audio which is converted to S/PDIF:
// Audio flows: BM83 -> I2S -> DIT4096 -> S/PDIF -> PCM5122 DAC
The PCM5122 DAC is configured via I2C in pcm51xx.c.

Event system integration

Bluetooth events trigger callbacks registered by the handler:
// In handler initialization:
EventRegisterCallback(
    BT_EVENT_DEVICE_CONNECTED,
    &HandlerBTDeviceConnected,
    &Context
);

// In BT module:
EventTriggerCallback(BT_EVENT_DEVICE_CONNECTED, &deviceId);
Common Bluetooth events:
  • BT_EVENT_BOOT - Module initialized
  • BT_EVENT_DEVICE_CONNECTED - Device connected
  • BT_EVENT_DEVICE_DISCONNECTED - Device disconnected
  • BT_EVENT_PLAYBACK_STATUS_CHANGE - Play/pause state changed
  • BT_EVENT_METADATA_UPDATE - Track info received
  • BT_EVENT_CALL_STATUS_UPDATE - Call state changed
  • BT_EVENT_CALLER_ID_UPDATE - Caller ID received
  • BT_EVENT_PBAP_CONTACT_RECEIVED - Contact downloaded
This event-driven architecture allows clean separation between Bluetooth protocol handling and vehicle integration logic.

Build docs developers (and LLMs) love