#include "mozen_protocol.h" #include #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); }