Skip to content

XU316 and MCU Communication Protocol Reference

1. Basic Data Structures

1.1 Ring Buffer

define RINGBUFFERSIZE 256 // Size of the ring buffer used for UART communication caching
typedef struct {
uint8t buffer[RINGBUFFERSIZE]; // Data storage area
volatile uint16t head; // Write position pointer
volatile uint16t tail; // Read position pointer
volatile uint16t count; // Current data count
} ringbuffert;

1.2 Audio Mode Definitions

// Audio mode configuration array, each mode contains 5 bytes of configuration data
static const uint8t audiomodes[][5] = {
{0x00, 0x80, 0xa9, 0x00, 0x01}, // USB-no-mqa mode
{0x00, 0x80, 0x01, 0x00, 0x02}, // UAC1 mode
{0x10, 0x80, 0x65, 0x10, 0x03}, // COAX mode
{0x00, 0x80, 0x65, 0x10, 0x04}, // OPT mode
{0x00, 0x80, 0xc5, 0x08, 0x05}, // SPDIF OUT mode
{0x00, 0x82, 0xd5, 0x81, 0x06}, // I2S IN mode
{0x20, 0x80, 0x65, 0x10, 0x07} // HDMI mode
};
// Corresponding mode names array
static const char *mode_names[] = {
"USB-no-mqa",
"UAC1",
"COAX",
"OPT",
"SPDIF OUT",
"I2S IN",
"HDMI"
};

2. Initialization and Configuration

2.1 Device Initialization Information Print

define AUDIOMODECOUNT (sizeof(audiomodes) / sizeof(audiomodes[0]))
void printinitinfo(void)
{
logdata(LOGUSER, "----------Device initialized----------\n");
// Print UAC1.0 device information
logdata(LOGUSER, "VID1: %02X%02X", mcudata.viduac1[0], mcudata.viduac1[1]);
logdata(LOGUSER, "PID1: %02X%02X", mcudata.piduac1[0], mcudata.piduac1[1]);
// Print UAC2.0 device information
logdata(LOGUSER, "VID2: %02X%02X", mcudata.viduac2[0], mcudata.viduac2[1]);
logdata(LOGUSER, "PID2: %02X%02X", mcudata.piduac2[0], mcudata.piduac2[1]);
// Print product information
logdata(LOGUSER, "Manufacturer: %s", mcudata.productmanufacturer);
logdata(LOGUSER, "Name: %s", mcudata.productname);
logdata(LOGUSER, "Serial: %s", mcudata.productserial);
// Print CRC check information
logdata(LOGUSER, "Basic Info CRC: %02X%02X%02X%02X",
mcudata.basicinfocrc[0], mcudata.basicinfocrc[1],
mcudata.basicinfocrc[2], mcudata.basicinfocrc[3]);
logdata(LOGUSER, "Power Config CRC: %02X%02X%02X%02X",
mcudata.powercfgcrc[0], mcudata.powercfgcrc[1],
mcudata.powercfgcrc[2], mcudata.powercfgcrc[3]);
}

2.2 Ring Buffer Initialization

void ringbufferinit(void) {
uartringbuffer.head = 0; // Initialize write position
uartringbuffer.tail = 0; // Initialize read position
uartringbuffer.count = 0; // Initialize data count
}

2.3 Product Information Initialization

void xu316init(void)
{
// Default audio mode configuration (USB UAC2.0)
uint8t audio_mode[5] = {0x00, 0x80, 0xa9, 0x00, 0x01};
// Configure UAC1.0 device information
mcu_data.vid_uac1[0] = 0x20;
mcu_data.vid_uac1[1] = 0xB1; // VID = 0x20B1
mcu_data.pid_uac1[0] = 0x00;
mcu_data.pid_uac1[1] = 0x17; // PID = 0x0017

// Configure UAC2.0 device information
mcu_data.vid_uac2[0] = 0x20;
mcu_data.vid_uac2[1] = 0xB1; // VID = 0x20B1
mcu_data.pid_uac2[0] = 0x00;
mcu_data.pid_uac2[1] = 0x16; // PID = 0x0016

// Configure product information
memcpy(mcu_data.product_manufacturer, "Phaten", 6);
// Set product name
memcpy(mcu_data.product_name, "XMOS XU316", 11);
// Set product serial number
memcpy(mcu_data.product_serial, "123456789ABCDEF", 15);

// Calculate and store CRC32 checksum for basic information (56 bytes)
crc = calculate_crc32(mcu_data.vid_uac1, 56);
mcu_data.basic_info_crc[0] = (crc >> 24) & 0xFF;
mcu_data.basic_info_crc[1] = (crc >> 16) & 0xFF;
mcu_data.basic_info_crc[2] = (crc >> 8) & 0xFF;
mcu_data.basic_info_crc[3] = crc & 0xFF;


// Initialize device status and audio configuration
mcu_data.startup_status = 0x00;
memcpy(&mcu_data.audio_mode, audio_mode, 5);

// Configure mute duration (400ms)
mcu_data.mute_duration[0] = MUTE_DURATION_CONFIG >> 8;
mcu_data.mute_duration[1] = MUTE_DURATION_CONFIG;

// Initialize volume settings
mcu_data.mic_volume = MIC_VOLUME_CONFIG;
// Initialize left and right channel DAC volumes
mcu_data.dac_l_volume = DAC_L_VOLUME_CONFIG;
mcu_data.dac_r_volume = DAC_R_VOLUME_CONFIG;

// Calculate and store CRC32 checksum for power configuration (10 bytes)
crc = calculate_crc32((uint8_t *)&mcu_data.audio_mode, 0x0a);
mcu_data.power_cfg_crc[0] = (crc >> 24) & 0xFF;
mcu_data.power_cfg_crc[1] = (crc >> 16) & 0xFF;
mcu_data.power_cfg_crc[2] = (crc >> 8) & 0xFF;
mcu_data.power_cfg_crc[3] = crc & 0xFF;


// Initialize communication buffer
ring_buffer_init();

}

// 3. Communication Buffer Operations // 3.1 Ring Buffer Operation Functions

// Write data to ring buffer

uint8_t ring_buffer_write(uint8_t *data, uint16_t len) {
uint16_t i;
for(i = 0; i < len; i++) {
if(uart_ring_buffer.count >= RING_BUFFER_SIZE) {
return 0; // Buffer full, write failed
}
uart_ring_buffer.buffer[uart_ring_buffer.head] = data[i];
uart_ring_buffer.head = (uart_ring_buffer.head + 1) % RING_BUFFER_SIZE;
uart_ring_buffer.count++;
}
return 1; // Write successful
}

// Read data from ring buffer
uint8_t ring_buffer_read(uint8_t *data, uint16_t len) {
uint16_t i;
if(uart_ring_buffer.count < len) {
return 0; // Insufficient data
}
for(i = 0; i < len; i++) {
data[i] = uart_ring_buffer.buffer[uart_ring_buffer.tail];
uart_ring_buffer.tail = (uart_ring_buffer.tail + 1) % RING_BUFFER_SIZE;
uart_ring_buffer.count--;
}
return 1; // Read successful
}

// Peek data (without moving read pointer)
uint8_t ring_buffer_peek(uint8_t *data, uint16_t len) {
uint16_t i;
uint16_t temp_tail = uart_ring_buffer.tail;
for(i = 0; i < len; i++) {
data[i] = uart_ring_buffer.buffer[temp_tail];
temp_tail = (temp_tail + 1) % RING_BUFFER_SIZE;
}
return 0; // Peek successful
}

// 4. Checksum Calculation // 4.1 CRC32 Calculation

uint32_t calculate_crc32(const uint8_t *buffer, uint32_t length)
{
uint32_t crc = 0xFFFFFFFF;
const uint32_t poly = 0xEDB88320; // CRC32 polynomial

for (size_t i = 0; i < length; i++) {
crc ^= buffer[i];
for (int j = 0; j < 8; j++) {
if (crc & 1) {
crc = (crc >> 1) ^ poly;
} else {
crc >>= 1;
}
}
}
return ~crc; // Return CRC32 checksum value
}

// Calculate simple checksum
uint8_t xu316_calc_checksum(uint8_t *data, uint8_t len)
{
uint8_t sum = 0;
for (uint8_t i = 0; i < len; i++) {
sum += data[i];
}
return sum;
}

// 5. Communication Protocol Processing // 5.1 Frame Validation // Verify frame data integrity

uint8_t uart_frame_check(uint8_t *buf, uint8_t len)
{
// Check minimum length requirement
if (len < 6) { // Header(2) + Version(1) + Command(1) + Length(2) + Checksum(1)
return 0;
}

// Verify frame header
if (buf[0] != FRAME_HEADER_H || buf[1] != FRAME_HEADER_L) {
return 0;
}

// Get data length (big-endian mode)
uint16_t data_len = buf[4];

// Verify data length validity
if (data_len > 256 || len < (data_len + 6)) {
return 0;
}

// Calculate and verify checksum
uint8_t sum = xu316_calc_checksum(buf, data_len + 5);
if (sum != buf[data_len + 5]) {
return 0;
}

LOG_TEMP(LOG_RECV, "", buf, len);
return 1;
}

// 5.2 Data Frame Encapsulation

int xu316_pack_frame(uint8_t cmd, uint8_t *data, uint8_t len)
{
uint8_t tx_data[256] = {0};
if (len >= 255) {
return 0;
}

// Build frame header
tx_data[0] = FRAME_HEADER_H;
tx_data[1] = FRAME_HEADER_L;
tx_data[2] = PROTOCOL_VERSION_RX;
tx_data[3] = cmd;
tx_data[4] = len;

// Copy data payload
if (data && len > 0) {
memcpy(tx_data + 5, data, len);
}

// Calculate and add checksum
tx_data[len + 5] = xu316_calc_checksum(tx_data, len + 5);
len += 6;

// Send data
usart_dma_send(tx_data, len);
LOG_INFO("Sending frame: cmd=0x%02X, len=%d", cmd, len);
LOG_TEMP(LOG_SEND, "", tx_data, len);
return len;
}

// 5.3 Frame Length Check

int check_frame_length(uint8_t *buf, uint16_t len) {
// Verify frame header
if (buf[0] != FRAME_HEADER_H || buf[1] != FRAME_HEADER_L) {
LOG_DEBUG("Frame header check failed %02x %02x", buf[0], buf[1]);
return -1;
}
// Get data length (big-endian mode)
int data_len = buf[4];
// Return complete frame length
return data_len + 6;
}

// 5.4 Data Reception Processing
void uart_data_process(void)
{
uint8_t peek_buffer[8]; // Peek buffer
uint8_t process_buffer[256]; // Processing buffer
int frame_length;

while(uart_ring_buffer.count >= 6) { // Need at least 6 bytes to start checking
// Peek frame header information
ring_buffer_peek(peek_buffer, 6);

// Debug output
for(int i = 0; i < 6; i++) {
LOG_DEBUG("peek_buffer[%d]: %02x", i, peek_buffer[i]);
}

// Check frame length
frame_length = check_frame_length(peek_buffer, 6);
if(frame_length < 0) {
// Invalid frame, discard one byte
uint8_t dummy;
ring_buffer_read(&dummy, 1);
LOG_ERROR("Frame length check failed %02x", dummy);
continue;
}

// Check if there's enough data
if(uart_ring_buffer.count < frame_length) {
LOG_ERROR("Not enough data");
break; // Wait for more data
}

// Read and process complete frame
if(ring_buffer_read(process_buffer, frame_length)) {
uart_data_parse();
}
}
}

// 5.5 Data Parsing Processing

int uart_data_parse(void)
{
int ret = 0;
uint8_t cmd = 0;
uint16_t data_len = 0;
uint16_t rx_len = 0;
uint8_t tmp;
static uint8_t buffer[256] = {0};
rx_len = g_rx_count;

// Verify frame integrity
ret = uart_frame_check((uint8_t *)g_rx_data, rx_len);
if (ret == 0) {
LOG_ERROR("Frame check failed %d", rx_len);
LOG_TEMP(LOG_RECV, "", g_rx_data, rx_len);
return -1;
}

// Parse frame information
data_len = rx_len - 6;
cmd = g_rx_data[3];
if (g_rx_data[4] != data_len) {
LOG_ERROR("Data length mismatch: expected %d, got %d", g_rx_data[4], data_len);
return -1;
}

LOG_DEBUG("Received frame: cmd=0x%02X, len=%d", cmd, data_len);
LOG_VERBOSE("--------------------------------");
LOG_VERBOSE("cmd : %02X", cmd);
memcpy(buffer, g_rx_data + 5, data_len);

// Process data according to command type
switch (cmd) {
case 0x00: // Startup information
// ... Process startup information ...
break;

case 0x01: // Basic information
// ... Process basic information ...
break;

case 0x02: // Power configuration
// ... Process power configuration ...
break;

// ... Other command processing ...

default:
LOG_ERROR("Unknown command: 0x%02X", cmd);
break;
}

return ret;
}