Files
2026-04-09 10:14:20 +08:00

316 lines
11 KiB
C

#include "mozen_protocol.h"
#include <string.h>
#include "bsp_uart.h"
// --- Helper Functions ---
/*
* Calculate CRC16 for data
* @param data: Pointer to data
* @param length: Length of data
* @return: CRC16 value
* @note: This function is used for Modbus CRC16
*/
uint16_t mozen_protocol_crc16_modbus(const uint8_t *data, uint16_t length)
{
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < length; i++)
{
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++)
{
if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001;
else crc >>= 1;
}
}
return crc;
}
/*
* Calculate checksum for data
* @param data: Pointer to data
* @param len: Length of data
* @return: Checksum value
* @note: This function is used for legacy checksum protocols
*/
uint16_t mozen_protocol_checksum(const uint8_t *data, uint32_t len)
{
uint16_t sum = 0;
for (uint32_t i = 0; i < len; ++i)
{
sum += data[i];
}
return sum;
}
// --- Internal Functions ---
/*
* Reset RX state
* @param prot: Pointer to mozen_protocol_t structure
* @note: This function is called automatically by mozen_protocol_process_rx
*/
static void mozen_protocol_reset_rx(mozen_protocol_t *prot)
{
prot->rx_state = MOZEN_RX_STATE_WAIT_SOF1; // Reset to wait for SOF1
prot->rx_index = 0; // Reset index
prot->expected_data_len = 0; // Reset expected data length
}
/*
* Dispatch received frame
* @param prot: Pointer to mozen_protocol_t structure
* @note: This function is called automatically by mozen_protocol_process_rx
* when a complete frame is received
*/
static void mozen_protocol_dispatch(mozen_protocol_t *prot)
{
if (prot->rx_index < sizeof(mozen_frame_header_t) + 2) return; // Too short
uint16_t received_crc; // Extract CRC from frame
mozen_frame_header_t header;
memcpy(&header, prot->rx_buffer, sizeof(mozen_frame_header_t));
if (header.sof != MOZEN_PROTOCOL_SOF) return;
uint16_t full_frame_len = header.frame_length;
uint32_t total_len = (uint32_t)full_frame_len + 2; // +2 for SOF and CRC
if (total_len > prot->rx_index || total_len > prot->rx_buffer_size) return;
uint16_t data_len = full_frame_len - sizeof(mozen_frame_header_t); // Exclude header
const uint8_t *data_ptr = prot->rx_buffer + sizeof(mozen_frame_header_t); // Point to data
memcpy(&received_crc, prot->rx_buffer + full_frame_len, sizeof(uint16_t)); // Extract CRC from frame
// 1. Raw frame handler check (e.g. for legacy checksum protocols)
if (prot->raw_frame_handler)
{
if (prot->raw_frame_handler(prot->user_context, prot->rx_buffer, total_len)) return; // Handled by custom raw handler
}
// 2. Standard CRC16 check
uint16_t calculated_crc = mozen_protocol_crc16_modbus(prot->rx_buffer, full_frame_len);
if (received_crc != calculated_crc) return; // CRC Error
// 3. Dispatch to registered handlers
for (uint8_t i = 0; i < prot->handler_count; i++)
{
if (prot->handlers[i].cmd_id == header.command_id)
{
prot->handlers[i].handler(prot->user_context, header.command_id, data_ptr, data_len);
return;
}
}
// 4. Default handler fallback
if (prot->default_handler)
{
prot->default_handler(prot->user_context, header.command_id, data_ptr, data_len);
}
}
// --- API Functions ---
/*
* Initialize mozen_protocol_t structure
* @param prot: Pointer to mozen_protocol_t structure
* @param rx_buf: Pointer to RX buffer
* @param rx_buf_size: Size of RX buffer
* @param tx_buf_main: Pointer to TX buffer
* @param tx_buf_irq: Pointer to IRQ TX buffer (optional)
* @param tx_buf_size: Size of TX buffer
* @param tx_fn: TX function
* @param user_context: User context for TX function
* @note: This function should be called once before using the protocol
*/
void mozen_protocol_init(mozen_protocol_t *prot,
uint8_t *rx_buf, uint16_t rx_buf_size,
uint8_t *tx_buf_main, uint8_t *tx_buf_irq, uint16_t tx_buf_size,
mozen_tx_fn_t tx_fn, void *user_context)
{
if (!prot) return;
memset(prot, 0, sizeof(mozen_protocol_t));
prot->rx_buffer = rx_buf;
prot->rx_buffer_size = rx_buf_size;
prot->tx_buffer_main = tx_buf_main;
prot->tx_buffer_irq = tx_buf_irq ? tx_buf_irq : tx_buf_main;
prot->tx_buffer_size = tx_buf_size;
prot->tx_fn = tx_fn;
prot->user_context = user_context;
mozen_protocol_reset_rx(prot);
}
/*
* Register a command handler
* @param prot: Pointer to mozen_protocol_t structure
* @param cmd_id: Command ID
* @param handler: Command handler function
* @return: 0 on success, -1 on failure
* @note: This function can be called multiple times to register multiple handlers
*/
int mozen_protocol_register_handler(mozen_protocol_t *prot, uint8_t cmd_id, mozen_cmd_handler_t handler)
{
if (!prot || !handler) return -1;
if (prot->handler_count >= MOZEN_MAX_HANDLERS) return -1;
prot->handlers[prot->handler_count].cmd_id = cmd_id;
prot->handlers[prot->handler_count].handler = handler;
prot->handler_count++;
return 0;
}
/*
* Process RX buffer
* @param prot: Pointer to mozen_protocol_t structure
* @param current_time_ms: Current time in milliseconds
* @note: This function should be called periodically to process incoming data
*/
void mozen_protocol_set_default_handler(mozen_protocol_t *prot, mozen_cmd_handler_t handler)
{
if (prot) prot->default_handler = handler;
}
/*
* Set raw frame handler
* @param prot: Pointer to mozen_protocol_t structure
* @param handler: Raw frame handler function
* @note: This function can be used to handle legacy checksum protocols or custom protocols
*/
void mozen_protocol_set_raw_handler(mozen_protocol_t *prot, mozen_raw_frame_handler_t handler)
{
if (prot) prot->raw_frame_handler = handler;
}
/*
* Feed a byte to the protocol
* @param prot: Pointer to mozen_protocol_t structure
* @param byte: Byte to feed
* @param current_time_ms: Current time in milliseconds
* @note: This function should be called whenever a new byte is received
*/
void mozen_protocol_feed_byte(mozen_protocol_t *prot, uint8_t byte, uint32_t current_time_ms)
{
if (!prot || !prot->rx_buffer) return;
prot->last_rx_time_ms = current_time_ms;
switch (prot->rx_state)
{
case MOZEN_RX_STATE_WAIT_SOF1:
if (byte == (MOZEN_PROTOCOL_SOF & 0xFF))
{
prot->rx_buffer[0] = byte;
prot->rx_index = 1;
prot->rx_state = MOZEN_RX_STATE_WAIT_SOF2;
}
break;
case MOZEN_RX_STATE_WAIT_SOF2:
if (byte == (MOZEN_PROTOCOL_SOF >> 8))
{
prot->rx_buffer[1] = byte;
prot->rx_index = 2;
prot->rx_state = MOZEN_RX_STATE_WAIT_HEADER;
}
else { mozen_protocol_reset_rx(prot);}
break;
case MOZEN_RX_STATE_WAIT_HEADER: // Wait for frame header
prot->rx_buffer[prot->rx_index++] = byte;
if (prot->rx_index >= sizeof(mozen_frame_header_t))
{
mozen_frame_header_t header;
memcpy(&header, prot->rx_buffer, sizeof(mozen_frame_header_t));
prot->expected_data_len = header.frame_length - sizeof(mozen_frame_header_t) + 2; // +2 for CRC
if ((sizeof(mozen_frame_header_t) + prot->expected_data_len) > prot->rx_buffer_size)
{
mozen_protocol_reset_rx(prot);
return;
}
prot->rx_state = MOZEN_RX_STATE_WAIT_DATA;
}
break;
case MOZEN_RX_STATE_WAIT_DATA:
prot->rx_buffer[prot->rx_index++] = byte;
if (prot->rx_index >= (sizeof(mozen_frame_header_t) + prot->expected_data_len))
{
mozen_protocol_dispatch(prot);
mozen_protocol_reset_rx(prot);
} else if (prot->rx_index >= prot->rx_buffer_size) {
// Should not happen due to header check, but safe guard against buffer overflow
mozen_protocol_reset_rx(prot);
}
break;
}
}
/*
* Reset the protocol state
* @param prot: Pointer to mozen_protocol_t structure
* current_time_ms: Current time in milliseconds
* timeout_ms: Timeout in milliseconds
* @note: This function should be called when a timeout occurs or when a frame is received
*/
void mozen_protocol_tick(mozen_protocol_t *prot, uint32_t current_time_ms, uint32_t timeout_ms)
{
if (!prot) return;
if (prot->rx_state != MOZEN_RX_STATE_WAIT_SOF1)
{
// Calculate diff taking into account potential integer overflow/wraparound
uint32_t time_diff = current_time_ms - prot->last_rx_time_ms;
if (time_diff > timeout_ms)
{
mozen_protocol_reset_rx(prot);
}
}
}
/*
* Send a frame
* @param prot: Pointer to mozen_protocol_t structure
* @param command_id: Command ID
* @param data: Data to send
* @param length: Length of data
* @param use_irq_buffer: Use IRQ buffer for sending
* @return: 0 on success, -1 on error
* @note: This function sends a frame using the provided data and command ID
*/
int mozen_protocol_send_frame(mozen_protocol_t *prot, uint8_t command_id, const uint8_t* data, uint16_t length, bool use_irq_buffer)
{
if (!prot || !prot->tx_fn) return -1;
uint16_t frame_length = sizeof(mozen_frame_header_t) + length; //Total length after removing CRC
uint16_t total_length = frame_length + 2; // Total length including CRC
if (total_length > prot->tx_buffer_size) return -1;
// During the transmission stage, double buffering is employed to prevent the data frames of the main loop from overlapping with the interrupt ACKs.
// The sensor data frames and ACKs may be sent concurrently in different contexts.
// The two static buffers can avoid overwriting and competition.
// The ACKs use an independent buffer to prevent interference with large packet transmission.
uint8_t *active_buffer = use_irq_buffer ? prot->tx_buffer_irq : prot->tx_buffer_main;
mozen_frame_header_t header;
header.sof = MOZEN_PROTOCOL_SOF;
header.command_id = command_id;
header.frame_length = frame_length;
memcpy(active_buffer, &header, sizeof(mozen_frame_header_t));
if (data && length > 0)
{
memcpy(active_buffer + sizeof(mozen_frame_header_t), data, length);
}
uint16_t crc = mozen_protocol_crc16_modbus(active_buffer, frame_length);
memcpy(active_buffer + frame_length, &crc, sizeof(uint16_t));
return prot->tx_fn(active_buffer, total_length);
}