The BlueBus bootloader provides a UART-based protocol for firmware upgrades and device management. It runs on startup with a 100ms timeout window, during which it listens for upgrade commands.
Protocol specification
Packet structure
All communication follows this packet format:
<Command Byte> <Length> <Data...> <XOR Checksum>
| Field | Size | Description |
|---|
| Command | 1 byte | Protocol command identifier |
| Length | 1 byte | Total packet length (including command, length, and checksum) |
| Data | 0-252 bytes | Command payload (minimum 1 byte) |
| Checksum | 1 byte | XOR of all preceding bytes |
At least one data byte is required per the protocol, though it can be 0x00 for commands without parameters.
Checksum calculation
From protocol.c:ProtocolValidatePacket():
uint8_t chk = packet->command;
chk = chk ^ packet->dataSize;
for (idx = 0; idx < msgSize; idx++) {
chk = chk ^ packet->data[idx];
}
chk = chk ^ validation; // Should equal 0 for valid packet
UART configuration
- Baud rate: 115200
- Parity: Odd
- Data bits: 8
- Stop bits: 1
- Flow control: None
The application firmware may use different UART settings (typically no parity). Tools must reconfigure the serial port when switching between application and bootloader modes.
Command set
Request platform (0x00)
Query the bootloader platform identifier.
# Request
command = 0x00
data = [0x00] # Dummy byte
packet = generate_packet(command, data)
Platform response (0x01)
Returns the platform string (e.g., BLUEBUS_BOOTLOADER_2_4).
# Response contains ASCII string
platform = 'BLUEBUS_BOOTLOADER_2_4'
Flash operations
Erase flash request (0x02)
Erases all application memory from 0x1800 to 0xAB000.
command = 0x02
data = [0x00]
The bootloader iterates through the application region:
uint32_t address = 0x1800; // BOOTLOADER_APPLICATION_START
while (address <= 0xAB000) {
FlashErasePage(address);
address += 1024; // Page size
}
Erase flash response (0x03)
Acknowledges erase completion.
Flash erase can take several seconds. The bootloader does not respond until the entire erase operation completes.
Write data request (0x04)
Writes a block of firmware data to flash.
Packet structure:
[0x04] [Length] [Addr2] [Addr1] [Addr0] [Data0] [Data1] ... [DataN] [XOR]
- Address: 3 bytes (24-bit big-endian)
- Data: 246 bytes (82 instructions × 3 bytes each)
From the Python tools:
row_data = []
addr_bytes = [b for b in pack('>I', address)]
addr_bytes.pop(0) # Remove MSB (always 0)
for b in addr_bytes:
row_data.append(b)
# Add 82 instructions (246 bytes)
for addr in range(82 * 2): # 164 program memory addresses
if addr % 2 == 0:
op_bytes = pack('>I', get_opcode(addr))
# Append 3 bytes per instruction
Write data response OK (0x05)
Write succeeded.
Write data response error (0x06)
Write failed. From protocol.c:ProtocolFlashWrite(), this occurs when:
- Flash write operation returns an error
- Data validation fails
Application control
Start application request (0x09)
Exits the bootloader and jumps to the application.
From main.c:
// Set IVT to application mode
IVT_MODE = IVT_MODE_APP;
// Jump to application vector
void (*appptr)(void);
appptr = (void (*)(void))BOOTLOADER_APPLICATION_VECTOR; // 0x2000
appptr();
Start application response (0x0A)
Sent before jumping to application (50ms delay to ensure transmission).
Firmware version request (0x0B)
Reads firmware version from EEPROM.
Firmware version response (0x0C)
Returns 3 bytes: [Major] [Minor] [Patch]
uint8_t response[] = {
EEPROMReadByte(0x02), // Major
EEPROMReadByte(0x03), // Minor
EEPROMReadByte(0x04) // Patch
};
Read serial number request (0x0D)
Read serial number response (0x0E)
Returns 2 bytes: [MSB] [LSB] (16-bit serial number)
Write serial number request (0x0F)
Writes serial number to EEPROM (only if currently 0xFFFF).
Packet: [0x0F] [Length] [MSB] [LSB] [XOR]
Write serial number response OK (0x10)
Write serial number response error (0x11)
Serial number already set (can only be written once).
Read build date request (0x12)
Read build date response (0x13)
Returns 2 bytes: [Week] [Year] (ISO week date)
Write build date request (0x14)
Write build date response OK (0x15)
Write build date response error (0x16)
Build date already set.
Bluetooth management
BT mode request (0x07)
Switches UART connection to Bluetooth module (for direct firmware updates).
From protocol.c:ProtocolBTMode():
// Board v1: Switch TMUX154E to BT UART mode
UART_SEL = UART_SEL_BT;
// Board v2: Bridge system UART to BT UART
while (1) {
// Bidirectional UART forwarding loop
}
BT mode response (0x08)
BT DFU mode request (0x17)
Puts Bluetooth module into Device Firmware Update mode and bridges UART.
BT_MFB = 1;
BT_BOOT_TYPE_MODE = 0; // Pull pin low for "Test" mode
BT_BOOT_TYPE = 0;
// Wait 150ms
BT_RST = 1; // Release reset
BT DFU mode response (0x18)
Error responses
Bad packet response (0xFF)
Checksum validation failed. The bootloader:
- Resets the RX queue
- Requests retransmission
Packet timeout (0xFE)
No complete packet received within 100ms. From protocol.c:ProtocolProcessQueue():
if ((now - lastRx) >= 100) { // PROTOCOL_PACKET_TIMEOUT
UARTRXQueueReset(uart);
ProtocolSendPacket(uart, 0xFE, 0, 0);
}
Bootloader operation
Startup sequence
From main.c:main():
Initialize hardware
- Set all ports to digital mode
- Configure pin directions (inputs/outputs)
- Initialize timer and EEPROM (SPI)
- Set up UART2 at 115200 baud, odd parity
Check boot mode
uint8_t BOOT_MODE = BOOT_MODE_APPLICATION;
unsigned char bootMode = EEPROMReadByte(CONFIG_BOOTLOADER_MODE);
if (bootMode != 0x00 || RECOVERY_STATUS == 0) {
BOOT_MODE = BOOT_MODE_BOOTLOADER;
TimerEnableLED(); // Flash LED to indicate bootloader mode
}
Wait for commands
while ((BOOT_MODE == BOOT_MODE_BOOTLOADER) ||
TimerGetMillis() <= 100) { // BOOTLOADER_TIMEOUT
// Process incoming packets
if (queueSize >= PROTOCOL_PACKET_MIN_SIZE) {
ProtocolProcessMessage(&systemUart, &BOOT_MODE);
}
}
The bootloader remains active if:
- Bootloader mode flag is set in EEPROM
- Recovery pin (RD5) is pulled low
- Valid packet received within 100ms timeout
Jump to application
// Mark bootloader mode as "application" for next boot
EEPROMWriteByte(CONFIG_BOOTLOADER_MODE, 0xAB);
// Clean up peripherals
UARTDestroy(SYSTEM_UART_MODULE);
EEPROMDestroy();
TimerDestroy();
// Switch interrupt vectors
IVT_MODE = IVT_MODE_APP;
// Jump to application
void (*appptr)(void) = (void (*)(void))0x2000;
appptr();
Mode locking
Once the bootloader receives a valid packet, it locks into bootloader mode:
if (packet.status == PROTOCOL_PACKET_STATUS_OK) {
if (*BOOT_MODE == 0) {
*BOOT_MODE = BOOT_MODE_BOOTLOADER; // Lock in bootloader
}
}
This prevents the 100ms timeout from expiring during firmware upgrades.
Memory protection
The bootloader protects itself from being overwritten:
if (address < BOOTLOADER_APPLICATION_START) { // 0x1800
// Skip this DWORD - protected bootloader region
address += 0x2;
index += 3;
} else {
// Write to application region
flashRes = FlashWriteDWORDAddress(address, data, data2);
}
Attempts to write to addresses 0x0000 - 0x17FF are silently skipped.
Example firmware upgrade sequence
Request platform
send([0x00, 0x03, 0x00, 0x03]) # Command, Length, Data, XOR
response = receive() # 0x01, Length, "BLUEBUS_BOOTLOADER_2_4", XOR
Erase flash
send([0x02, 0x03, 0x00, 0x01])
response = receive() # 0x03, 0x03, 0x00, 0x00
# Wait for erase completion (can take 2-3 seconds)
Write firmware blocks
for block in firmware_blocks:
# block = [Addr2, Addr1, Addr0, Data0...Data245]
send([0x04, len(block) + 3, *block, calculate_xor()])
response = receive() # 0x05 (OK) or 0x06 (Error)
Start application
send([0x09, 0x03, 0x00, 0x0A])
response = receive() # 0x0A, 0x03, 0x00, 0x09
# Bootloader jumps to application after 50ms
Interrupt handling
The bootloader implements trap handlers for fault conditions:
void __attribute__ ((__interrupt__, auto_psv)) _OscillatorFail(void) {
INTCON1bits.OSCFAIL = 0;
ON_LED = 0;
while (1); // Halt on error
}
void __attribute__ ((__interrupt__, auto_psv)) _AddressError(void)
void __attribute__ ((__interrupt__, auto_psv)) _StackError(void)
void __attribute__ ((__interrupt__, auto_psv)) _MathError(void)
void __attribute__ ((__interrupt__, auto_psv)) _NVMError(void)
void __attribute__ ((__interrupt__, auto_psv)) _GeneralError(void)
All traps turn off the LED and halt the processor.
EEPROM storage
The bootloader uses an external SPI EEPROM for persistent configuration:
| Address | Size | Content |
|---|
0x00 | 2 bytes | Serial number (MSB, LSB) |
0x02 | 3 bytes | Firmware version (Major, Minor, Patch) |
0x05 | 2 bytes | Build date (Week, Year) |
0x07 | 1 byte | Bootloader mode flag |
Serial number and build date can only be written once (when EEPROM values are 0xFF). This prevents accidental modification during production.