diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b629e0b8..0f7fc5d3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -37,7 +37,7 @@ Tinymovr consists of **two separate projects**: - **Build**: Python package (pip install) ### Communication -- **Physical**: CAN bus (CAN 2.0B extended ID) +- **Physical**: CAN bus (CAN 2.0B extended ID), UART - **Protocol**: Avlos (schema-driven, auto-generated from YAML) - **Direction**: Bidirectional (host ↔ firmware) @@ -420,7 +420,7 @@ Application **Toolchain**: GNU ARM Embedded (arm-none-eabi-gcc) **Build Tool**: GNU Make **Targets**: `debug`, `release`, `clean` -**Board Selection**: `REV=R52` (or R53, M51, etc.) +**Board Selection**: `REV=R53` (or R52, M51, etc.) **Linker Script**: [firmware/pac55xx.ld](firmware/pac55xx.ld) - Memory layout diff --git a/docs/interfaces/interfaces.rst b/docs/interfaces/interfaces.rst index d2ba9424..15412e7f 100644 --- a/docs/interfaces/interfaces.rst +++ b/docs/interfaces/interfaces.rst @@ -1,3 +1,5 @@ +.. _interfaces: + *************** Comm Interfaces *************** @@ -27,7 +29,7 @@ For a detailed description, please see :ref:`integrating` and :ref:`api-referenc UART ---- -As an alternative to CAN Bus, Tinymovr offers UART-based (serial) communication. The protocol is much simpler than CAN and mainly designed for troubleshooting or testing in the absence of CAN hardware. +Tinymovr offers UART-based (serial) communication as an alternative to CAN Bus. The UART interface uses the same Avlos binary protocol as CAN, providing **full API parity** with all endpoints available over both interfaces. .. warning:: The UART port on Tinymovr is NOT 5V tolerant. Applying 5V voltage will immediately damage the onboard PAC5527 controller. Please use only 3.3V for UART communication. @@ -35,302 +37,107 @@ As an alternative to CAN Bus, Tinymovr offers UART-based (serial) communication. .. warning:: The UART port on Tinymovr offers a 3.3v output for driving very light loads (30mA absolute max). Tinymovr cannot be powered by this pin. In addition, most UART adapters offer 5V power, which is incompatible with Tinymovr. **In short: If in doubt, leave this pin disconnected**. -Protocol Description -#################### - -The UART port is TTL at 115200 baud. A regular FTDI-style USB to UART adapter should be sufficient. - -UART communication is based on a simple human-readable protocole dubbed the "dot protocol", because the dot is the command starting character. The protocol is response-only, meaning that Tinymovr will only respond to commands initiated by the client, it will never initiate a transmission on it's own. - -The command template is as follows: - -.. code-block:: shell - - .Cxxxx - -The command begins with a dot. The next single character identifies the command. The characters following the second one are optional values to pass to write commands. Read commands only include the dot and command character. The command is finalized with a newline character (\n, not shown above). - -For instance, to get the current position estimate: - -.. code-block:: shell - - command: .p - response: 1000 - -To set the velocity estimate in encoder ticks: - -.. code-block:: shell - - command: .V10000 - (no response) - -The values passed or returned are always integers scaled by the mentioned factor (see command reference below). - -Note that command characters are case-sensitive, i.e. capitals and small represent different commands. As a convention, capital letters are setters and small are getters, where applicable. - -Command Reference -################# - -.Z -== - -Transition to idle state. - -Example - -.. code-block:: shell - - .Z - 0 - -.Q -== - -Transition to calibration state. - -Example - -.. code-block:: shell - - .Q - 0 - -.A -== - -Transition to closed loop control state. - -Example - -.. code-block:: shell - - .A - 0 - -.e -== - -Get the error code. - -Example - -.. code-block:: shell - - .e - 0 - -.p -== - -Get position estimate (ticks). - -Example - -.. code-block:: shell - - .p - 1000 - -.v -== - -Get velocity estimate (ticks/s). - - -Example - -.. code-block:: shell - - .v - -200 - -.i -== - -Get current (Iq) estimate (mA). - -Example - -.. code-block:: shell - - .i - 2000 - -.P -== - -Get/set position setpoint (ticks). - -Example - -.. code-block:: shell - - .P - 1000 - -.. code-block:: shell - - .P1000 - -.V -== - -Get/set velocity setpoint (ticks/s). - -Example - -.. code-block:: shell - - .V - -10000 - -.. code-block:: shell - - .V-10000 - -.I -== - -Get/set current (Iq) setpoint (mA). - -Example - -.. code-block:: shell - - .I - 1000 - -.. code-block:: shell - - .I1000 - -.W -== - -Get/set current (Iq) limit (mA). - -Example - -.. code-block:: shell - - .W - 10000 - -.. code-block:: shell - - .W15000 - -.Y -== - -Get/set position gain. - -Example - -.. code-block:: shell - - .Y - 25 - -.. code-block:: shell - - .Y25 - -.F -== - -Get/set velocity gain (x0.000001). - -Example - -.. code-block:: shell - - .F - 20 - -.. code-block:: shell - - .F20 - -.G -== - -Get/set velocity integrator gain (x0.001). - -Note that high values (e.g. above 10) may cause instability. - -Example - -.. code-block:: shell - - .G - 2 +Physical Layer +############## -.. code-block:: shell +- **Baud rate**: 115200 +- **Data bits**: 8 +- **Parity**: None +- **Stop bits**: 1 +- **Voltage level**: 3.3V TTL - .G2 +A standard FTDI-style USB to UART adapter at 3.3V is suitable. -.H -== +Binary Protocol +############### -Get/set motor phase resistance (mOhm). +UART uses a binary protocol with CRC-16 error detection. The protocol is request-response: Tinymovr only responds to commands initiated by the host. -Example +Frame Format +============ -.. code-block:: shell +All frames follow this structure: - .H - 200 +.. code-block:: text -.. code-block:: shell + | Sync (1) | Length (1) | Hash (1) | EP_ID (2) | CMD (1) | Payload (0-8) | CRC16 (2) | - .H 200 +- **Sync** (1 byte): Frame start marker, always ``0xAA`` +- **Length** (1 byte): Number of bytes after Length field, excluding CRC (min 4, max 12) +- **Hash** (1 byte): Low 8 bits of protocol hash (for version verification), or ``0x00`` to skip check +- **EP_ID** (2 bytes): Endpoint ID, little-endian (see :ref:`api-reference` for endpoint list) +- **CMD** (1 byte): Command type: ``0x00`` = read/call, ``0x01`` = write +- **Payload** (0-8 bytes): Data payload (for write commands or function arguments) +- **CRC16** (2 bytes): CRC-16-CCITT checksum, little-endian -.L -== +CRC Calculation +=============== -Get/set motor phase inductance (μH). +The CRC-16-CCITT algorithm is used: -Example +- **Polynomial**: 0x1021 +- **Initial value**: 0xFFFF +- **Input reflection**: No +- **Output reflection**: No -.. code-block:: shell +The CRC is calculated over all bytes from Sync to the end of Payload (excluding the CRC field itself). - .L - 2000 +Example: Read Bus Voltage +========================= -.. code-block:: shell +To read the bus voltage (endpoint ID 4): - .L 2000 +**Request frame** (hex): -.R -== +.. code-block:: text -Reset the MCU. + AA 04 00 04 00 00 [CRC16] + | | | | | +-- CMD: 0x00 (read) + | | | +--+-- EP_ID: 0x0004 (Vbus) + | | +-- Hash: 0x00 (skip check) + | +-- Length: 4 bytes + +-- Sync: 0xAA -Example +**Response frame** (hex): -.. code-block:: shell +.. code-block:: text - .R + AA 08 XX 04 00 00 [4-byte float] [CRC16] + | | | | | | +-- Payload: Vbus value as IEEE 754 float + | | | +--+--+-- EP_ID + CMD + | | +-- Hash: protocol hash (low 8 bits) + | +-- Length: 8 bytes + +-- Sync: 0xAA -.S -== +Example: Set Position Setpoint +============================== -Save board configuration. +To set position setpoint (endpoint ID 25) to 1000.0 ticks: -Example +**Request frame** (hex): -.. code-block:: shell +.. code-block:: text - .S + AA 08 00 19 00 01 00 00 7A 44 [CRC16] + | | | | | | +----------+-- Payload: 1000.0f (IEEE 754: 0x447A0000) + | | | | | +-- CMD: 0x01 (write) + | | | +--+-- EP_ID: 0x0019 (25 = controller.position.setpoint) + | | +-- Hash: 0x00 + | +-- Length: 8 bytes + +-- Sync: 0xAA -.X -== +Write commands do not return a response unless the endpoint is a function with a return value. -Erase board configuration and reset. +API Reference +############# -Example +UART provides access to all Avlos endpoints. See :ref:`api-reference` for the complete endpoint list with IDs, data types, and descriptions. -.. code-block:: shell +Common endpoints: - .X +- **ID 0**: ``protocol_hash`` - Protocol version hash (uint32) +- **ID 4**: ``Vbus`` - Bus voltage in volts (float) +- **ID 21**: ``controller.state`` - Controller state (uint8) +- **ID 25**: ``controller.position.setpoint`` - Position setpoint in ticks (float) +- **ID 42**: ``controller.calibrate()`` - Start calibration (void) +- **ID 43**: ``controller.idle()`` - Set idle mode (void) diff --git a/docs/protocol/integrating.rst b/docs/protocol/integrating.rst index a12d31f9..a122e50b 100644 --- a/docs/protocol/integrating.rst +++ b/docs/protocol/integrating.rst @@ -41,6 +41,74 @@ The above code block will instantiate a Tinymovr with CAN bus id of 1 and calibr tm.controller.velocity_mode() tm.controller.velocity.setpoint = 80000 +Direct UART Integration +####################### + +For embedded systems without CAN support, Tinymovr provides a UART interface using Avlos binary framing. This replaces the previous ASCII-based UART protocol with full API parity to CAN. + +.. note:: + The legacy ASCII UART protocol (dot-notation commands) has been removed. All UART communication now uses the Avlos binary frame format described below. + +UART Frame Format +----------------- + +All UART frames follow this structure: + +.. code-block:: text + + | Sync (1) | Length (1) | Hash (1) | EP_ID (2) | CMD (1) | Payload (0-8) | CRC16 (2) | + +- **Sync** (1 byte): ``0xAA`` - Frame start marker +- **Length** (1 byte): Bytes after Length field, excluding CRC (range: 4-12) +- **Hash** (1 byte): Low 8 bits of protocol hash, or ``0x00`` to skip version check +- **EP_ID** (2 bytes): Endpoint ID, little-endian +- **CMD** (1 byte): ``0x00`` = read/call, ``0x01`` = write +- **Payload** (0-8 bytes): Data for write commands (max 8 bytes) +- **CRC16** (2 bytes): CRC-16-CCITT checksum, little-endian + +Frame Size Limits +----------------- + +- **Minimum frame**: 8 bytes (read request with no payload) +- **Maximum frame**: 16 bytes (write request with 8-byte payload) +- **Maximum payload**: 8 bytes + +CRC-16-CCITT Calculation +------------------------ + +The CRC is calculated over all bytes from Sync through Payload (excluding CRC field): + +- **Polynomial**: 0x1021 +- **Initial value**: 0xFFFF +- **Input/Output reflection**: None + +Example (Python): + +.. code-block:: python + + def crc16_ccitt(data: bytes) -> int: + crc = 0xFFFF + for byte in data: + crc ^= byte << 8 + for _ in range(8): + if crc & 0x8000: + crc = (crc << 1) ^ 0x1021 + else: + crc <<= 1 + crc &= 0xFFFF + return crc + +Protocol Hash Behavior +---------------------- + +The protocol hash ensures firmware/client compatibility: + +- Include low 8 bits of ``protocol_hash`` (endpoint 0) in requests for version verification +- Use ``0x00`` to skip the hash check (useful for initial connection or debugging) +- Responses always include the actual protocol hash + +For physical layer details (baud rate, voltage levels) and complete examples, see :ref:`interfaces`. + BusRouter API ############# diff --git a/firmware/src/config.h b/firmware/src/config.h index 91644f40..e77f34c4 100644 --- a/firmware/src/config.h +++ b/firmware/src/config.h @@ -49,23 +49,3 @@ #define UART_ENUM UARTB #define UART_REF PAC55XX_UARTB #define UART_BAUD_RATE (115200) - -#define UART_I_SCALING_FACTOR ( 1000.0f ) -#define ONE_OVER_UART_I_SCALING_FACTOR ( 0.001f ) - -#define UART_R_SCALING_FACTOR ( 1000.0f ) -#define ONE_OVER_UART_R_SCALING_FACTOR ( 0.001f ) - -#define UART_L_SCALING_FACTOR ( 1000.0f ) -#define ONE_OVER_UART_L_SCALING_FACTOR ( 0.001f ) - -#define UART_VEL_GAIN_SCALING_FACTOR ( 1000000.0f ) -#define ONE_OVER_UART_VEL_GAIN_SCALING_FACTOR ( 0.000001f ) - -#define UART_VEL_INT_SCALING_FACTOR ( 1000.0f ) -#define ONE_OVER_UART_VEL_INT_SCALING_FACTOR ( 0.001f ) - -#define UART_IQ_LIMIT_SCALING_FACTOR ( 1000.f ) -#define ONE_OVER_UART_IQ_LIMIT_SCALING_FACTOR ( 0.001f ) - -#define UART_V_SCALING_FACTOR ( 1000.0f ) diff --git a/firmware/src/uart/uart_func.c b/firmware/src/uart/uart_func.c index 0be59193..10cb9080 100644 --- a/firmware/src/uart/uart_func.c +++ b/firmware/src/uart/uart_func.c @@ -465,208 +465,3 @@ void uart_init(UART_TYPE uart, uint32_t baudrate) uart_interrupt_enable(uart); } -//============================================================================== -/// @brief -/// read a byte from UART manually -/// -/// @param[in] -/// UART Type: -/// UARTA -/// UARTB -/// UARTC -/// UARTD -/// data: The data read back. -/// -/// @param[out] -/// result: the result of the UART byte read -/// -///@retval -/// 0 All is OK. -/// others Some error occurs. -/// -//============================================================================== -uint32_t uart_read_one(UART_TYPE uart, uint8_t *data) -{ - uint32_t result = PAC5XXX_OK; - uint32_t wait_tick = 0; - - PAC55XX_UART_TYPEDEF *uart_ptr; - - switch (uart) - { - case UARTA: - uart_ptr = PAC55XX_UARTA; - break; - - case UARTB: - uart_ptr = PAC55XX_UARTB; - break; - - case UARTC: - uart_ptr = PAC55XX_UARTC; - break; - - case UARTD: - uart_ptr = PAC55XX_UARTD; - break; - - default: - uart_ptr = PAC55XX_UARTA; - break; - } - - while (uart_ptr->LSR.RDR == 0) - { - wait_tick++; - if (wait_tick > DF_UART_BUSY_TICK) - { - result = PAC5XXX_ERROR; - break; - } - } - - *data = (uint8_t)uart_ptr->RBR.RBR; - - return result; -} - -//============================================================================== -///@brief -/// Write a byte to UART manually -/// -///@param[in] -/// UART Type: -/// UARTA -/// UARTB -/// UARTC -/// UARTD -/// data: The data to write. -/// -///@param[out] -/// result: the result of the UART byte write -/// -///@retval -/// 0 All is OK. -/// others Some error occurs. -/// -//============================================================================== -uint32_t uart_write_one(UART_TYPE uart, uint8_t data) -{ - uint32_t result = PAC5XXX_OK; - uint32_t wait_tick = 0; - - PAC55XX_UART_TYPEDEF *uart_ptr; - - switch (uart) - { - case UARTA: - uart_ptr = PAC55XX_UARTA; - break; - - case UARTB: - uart_ptr = PAC55XX_UARTB; - break; - - case UARTC: - uart_ptr = PAC55XX_UARTC; - break; - - case UARTD: - uart_ptr = PAC55XX_UARTD; - break; - - default: - uart_ptr = PAC55XX_UARTA; - break; - } - - uart_ptr->THR.THR = data; - - while (uart_ptr->LSR.TEMT == 0) - { - wait_tick++; - if (wait_tick > DF_UART_BUSY_TICK) - { - result = PAC5XXX_ERROR; - break; - } - } - - return result; -} - -//============================================================================== -///@brief -/// Write a serial bytes to UART manually -/// -///@param[in] -/// UART Type: -/// UARTA -/// UARTB -/// UARTC -/// UARTD -/// data_p: The data pointer to write. -/// byte_num: the number to write. -/// -///@param[out] -/// result: the result of the UART byte write -/// -///@retval -/// 0 All is OK. -/// others Some error occurs. -/// -//============================================================================== -uint32_t uart_write_multi(UART_TYPE uart, uint8_t *data_p, uint32_t byte_num) -{ - uint8_t *data; - data = data_p; - - uint32_t result = PAC5XXX_OK; - uint32_t wait_tick = 0; - uint32_t byte_num_temp; - - PAC55XX_UART_TYPEDEF *uart_ptr; - - switch (uart) - { - case UARTA: - uart_ptr = PAC55XX_UARTA; - break; - - case UARTB: - uart_ptr = PAC55XX_UARTB; - break; - - case UARTC: - uart_ptr = PAC55XX_UARTC; - break; - - case UARTD: - uart_ptr = PAC55XX_UARTD; - break; - - default: - uart_ptr = PAC55XX_UARTA; - break; - } - - for (byte_num_temp=0; byte_num_tempTHR.THR = *data; - data++; - - while (uart_ptr->LSR.TEMT == 0) - { - wait_tick++; - if (wait_tick > DF_UART_BUSY_TICK) - { - result = PAC5XXX_ERROR; - break; - } - } - - wait_tick = 0; - } - - return result; -} diff --git a/firmware/src/uart/uart_func.h b/firmware/src/uart/uart_func.h index 455c857a..f92bb632 100644 --- a/firmware/src/uart/uart_func.h +++ b/firmware/src/uart/uart_func.h @@ -16,17 +16,8 @@ #ifndef UART_FUNC_H #define UART_FUNC_H -#define DF_isr_for_UART #define DF_UART_PCLK HCLK_FREQ_HZ -#define DF_UART_BUSY_TICK (25000u) - -volatile uint8_t uart_read_buf[48]; -volatile uint8_t uart_write_buf[48]; -volatile uint32_t uart_read_count; extern void uart_init(UART_TYPE uart, uint32_t baudrate); -extern uint32_t uart_read_one(UART_TYPE uart, uint8_t *data); -extern uint32_t uart_write_one(UART_TYPE uart, uint8_t data); -extern uint32_t uart_write_multi(UART_TYPE uart, uint8_t *data, uint32_t byte_num); #endif diff --git a/firmware/src/uart/uart_interface.c b/firmware/src/uart/uart_interface.c index 5166208b..fea4cd7b 100644 --- a/firmware/src/uart/uart_interface.c +++ b/firmware/src/uart/uart_interface.c @@ -15,239 +15,172 @@ // * You should have received a copy of the GNU General Public License // * along with this program. If not, see . -#include "string.h" -#include "src/system/system.h" -#include "src/motor/motor.h" -#include "src/observer/observer.h" -#include "src/controller/controller.h" -#include "src/controller/trajectory_planner.h" -#include "src/adc/adc.h" -#include "src/nvm/nvm.h" -#include "src/can/can.h" -#include "src/utils/utils.h" -#include "src/uart/uart_lowlevel.h" -#include "src/uart/uart_interface.h" - -void UART_WriteAddr(uint8_t addr, int32_t data) +#include +#include +#include +#include +#include +#include + +// Protocol hash (low 8 bits) for version verification +static const uint8_t avlos_proto_hash_8 = (uint8_t)(avlos_proto_hash & 0xFF); +static const size_t endpoint_count = sizeof(avlos_endpoints) / sizeof(avlos_endpoints[0]); + +/** + * @brief Calculate CRC-16-CCITT using hardware CRC peripheral + * @param data Pointer to data buffer + * @param len Length of data in bytes + * @return 16-bit CRC value + */ +static uint16_t UART_calculate_crc16(const uint8_t *data, uint8_t len) { - switch (addr) + // Configure CRC peripheral for CRC-16-CCITT + PAC55XX_CRC->CTL.POLYSEL = CRC16_CCITT; + PAC55XX_CRC->CTL.DATAWIDTH = CRC_DATA_WIDTH_8BITS; + PAC55XX_CRC->CTL.INREF = 0; + PAC55XX_CRC->CTL.OUTREF = 0; + + // Set seed value + PAC55XX_CRC->SEED.CRCSEED = 0xFFFF; + + // Feed data bytes + for (uint8_t i = 0; i < len; i++) { - case 'P': // pos setpoint - controller_set_Iq_setpoint_user_frame(0); - controller_set_vel_setpoint_user_frame(0); - controller_set_pos_setpoint_user_frame(data); - controller_set_mode(CONTROLLER_MODE_POSITION); - break; - - case 'V': // vel setpoint - controller_set_Iq_setpoint_user_frame(0); - controller_set_vel_setpoint_user_frame(data); - controller_set_mode(CONTROLLER_MODE_VELOCITY); - controller_set_vel_setpoint_user_frame((float)data); - break; - - case 'I': // current setpoint - controller_set_mode(CONTROLLER_MODE_CURRENT); - controller_set_Iq_setpoint_user_frame((float)data * ONE_OVER_UART_I_SCALING_FACTOR); - break; - - case 'G': // velocity integrator gain - controller_set_vel_integral_gain((float)data * ONE_OVER_UART_VEL_INT_SCALING_FACTOR); - break; - - case 'Y': // Position gain - controller_set_pos_gain(data); - break; - - case 'F': // Velocity gain - controller_set_vel_gain(data * ONE_OVER_UART_VEL_GAIN_SCALING_FACTOR); - break; - - case 'H': // phase resistance - motor_set_phase_resistance((float)data * ONE_OVER_UART_R_SCALING_FACTOR); - break; - - case 'L': // phase inductance - motor_set_phase_inductance((float)data * ONE_OVER_UART_L_SCALING_FACTOR); - break; - - case 'M': // Set is motor gimbal? - motor_set_is_gimbal((bool)data); - break; - - case 'W': // Set Iq Limit - controller_set_Iq_limit((float)data * ONE_OVER_UART_IQ_LIMIT_SCALING_FACTOR); - break; - - case 'U': // CAN Baud Rate - CAN_set_kbit_rate((uint16_t)data); - break; - - case 'C': // CAN ID - CAN_set_ID((uint8_t)data); - break; - - case '<': // Max Decel - planner_set_max_decel((float)data); - break; - - case '>': // Max Accel - planner_set_max_accel((float)data); - break; - - case '^': // Max Vel - planner_set_max_vel((float)data); - break; - - case 'T': // Plan trajectory - planner_move_to_vlimit((float)data); - break; - - default: - // No action - break; + PAC55XX_CRC->DATAIN = data[i]; } + + // Return computed CRC + return (uint16_t)PAC55XX_CRC->OUT.CRCOUT; } -int32_t UART_ReadAddr(uint8_t addr) +/** + * @brief Send a response frame over UART + * @param ep_id Endpoint ID + * @param payload Pointer to payload data + * @param payload_len Length of payload + */ +static void UART_send_response(uint16_t ep_id, const uint8_t *payload, uint8_t payload_len) { - int32_t ret_val = 0; - switch (addr) - { - case 'b': // vbus value - ret_val = (int32_t)(system_get_Vbus() * UART_V_SCALING_FACTOR); - break; - - case 'e': // controller error + // Build response frame + // Format: | Sync (1) | Length (1) | Hash (1) | EP_ID (2) | CMD (1) | Payload (0-8) | CRC16 (2) | + uint8_t frame_len = UART_FRAME_HEADER_SIZE + payload_len; + + uart_tx_msg[0] = UART_SYNC_BYTE; + uart_tx_msg[1] = UART_FRAME_HEADER_SIZE - 2 + payload_len; // Length after length field, excluding CRC + uart_tx_msg[2] = avlos_proto_hash_8; + uart_tx_msg[3] = (uint8_t)(ep_id & 0xFF); // EP_ID low byte + uart_tx_msg[4] = (uint8_t)((ep_id >> 8) & 0xFF); // EP_ID high byte + uart_tx_msg[5] = AVLOS_CMD_READ; // Response is always a "read" result + + // Copy payload + if (payload_len > 0 && payload != NULL) { - // pass + memcpy(&uart_tx_msg[UART_FRAME_HEADER_SIZE], payload, payload_len); } - break; - - case 'p': // pos estimate - ret_val = user_frame_get_pos_estimate(); - break; - - case 'P': // pos setpoint - ret_val = controller_get_pos_setpoint_user_frame(); - break; - - case 'v': // vel estimate - ret_val = (int32_t)user_frame_get_vel_estimate(); - break; - - case 'V': // vel setpoint - ret_val = (int32_t)controller_get_vel_setpoint_user_frame(); - break; - - case 'i': // current estimate - ret_val = (int32_t)(controller_get_Iq_estimate_user_frame() * UART_I_SCALING_FACTOR); - break; - - case 'I': // current setpoint - ret_val = (int32_t)(controller_get_Iq_setpoint_user_frame() * UART_I_SCALING_FACTOR); - break; - - case 'G': // velocity integrator setpoint - ret_val = (int32_t)(controller_get_vel_integral_gain() * UART_VEL_INT_SCALING_FACTOR); - break; - - case 'H': // phase resistance - ret_val = (int32_t)(motor_get_phase_resistance() * UART_R_SCALING_FACTOR); - break; - - case 'L': // phase inductance - ret_val = (int32_t)(motor_get_phase_inductance() * UART_L_SCALING_FACTOR); - break; - - case 'W': // Get Iq Limit - ret_val = (int32_t)(controller_get_Iq_limit() * UART_IQ_LIMIT_SCALING_FACTOR); - break; - - case 'C': // CAN ID - ret_val = CAN_get_ID(); - break; - - case 'M': // Is motor gimbal? - ret_val = motor_get_is_gimbal(); - break; - - case 'Y': // - ret_val = controller_get_pos_gain(); - break; - - case 'F': // - ret_val = controller_get_vel_gain() * UART_VEL_GAIN_SCALING_FACTOR; - break; - - case 'Q': // calibrate - controller_set_state(CONTROLLER_STATE_CALIBRATE); - break; - - case 'A': // closed loop - controller_set_state(CONTROLLER_STATE_CL_CONTROL); - break; - - case 'Z': // idle - controller_set_state(CONTROLLER_STATE_IDLE); - break; - - case 'R': // reset mcu - system_reset(); - break; - - case 'S': // save config - nvm_save_config(); - break; - - case 'X': // erase config - nvm_erase(); - break; - - default: - // No action - break; - } - return ret_val; + + // Calculate CRC over entire frame (excluding CRC field itself) + uint16_t crc = UART_calculate_crc16(uart_tx_msg, frame_len); + uart_tx_msg[frame_len] = (uint8_t)(crc & 0xFF); // CRC low byte + uart_tx_msg[frame_len + 1] = (uint8_t)((crc >> 8) & 0xFF); // CRC high byte + + // Set total frame length including CRC + uart_tx_frame_len = frame_len + 2; + uart_tx_byte_idx = 0; + + // Enable transmit interrupt to send response + pac5xxx_uart_int_enable_THREI2(UART_REF, 1); } +/** + * @brief Process received UART message using Avlos protocol + * + * Frame format: + * | Sync (1) | Length (1) | Hash (1) | EP_ID (2) | CMD (1) | Payload (0-8) | CRC16 (2) | + */ void UART_process_message(void) { - int8_t addr = uart_rx_msg[1]; - int8_t len = ((int8_t)uart_rx_msg_len) - 3; - - // Ensure buffer is null-terminated - uart_rx_msg[uart_rx_msg_len] = '\0'; - - if (len > 0) + // Minimum frame size: Sync + Length + Hash + EP_ID(2) + CMD + CRC(2) = 8 bytes + if (uart_rx_msg_len < UART_FRAME_MIN_SIZE) { - // Write operation - int32_t n = atol(&(uart_rx_msg)[2]); - UART_WriteAddr(addr, n); + return; // Frame too short } - else if (len == 0) + + // Verify sync byte + if (uart_rx_msg[0] != UART_SYNC_BYTE) { - // Read operation - int32_t val = UART_ReadAddr(uart_rx_msg[1]); - UART_SendInt32(val); + return; // Invalid sync byte } - else + + // Get length field + uint8_t length = uart_rx_msg[1]; + uint8_t total_len = length + 2 + 2; // +2 for Sync+Length, +2 for CRC + + // Validate total length + if (total_len != uart_rx_msg_len || total_len > UART_FRAME_MAX_SIZE) { - // Error + return; // Length mismatch } -} - -void UART_SendInt32(int32_t val) -{ - (void)itoa(val, uart_tx_msg, 10); - for (uint8_t i = 0; i < UART_BYTE_LIMIT; i++) + + // Verify CRC (over all bytes except CRC itself) + uint8_t crc_offset = total_len - 2; + uint16_t received_crc = (uint16_t)uart_rx_msg[crc_offset] | + ((uint16_t)uart_rx_msg[crc_offset + 1] << 8); + uint16_t calculated_crc = UART_calculate_crc16(uart_rx_msg, crc_offset); + + if (received_crc != calculated_crc) { - if (uart_tx_msg[i] == '\0') - { - uart_tx_msg[i] = UART_LINEFEED; - break; - } + return; // CRC mismatch } - // Enable transmit interrupt to send reponse to host - pac5xxx_uart_int_enable_THREI2(UART_REF, 1); + + // Extract frame fields + uint8_t frame_hash = uart_rx_msg[2]; + uint16_t ep_id = (uint16_t)uart_rx_msg[3] | ((uint16_t)uart_rx_msg[4] << 8); + uint8_t cmd = uart_rx_msg[5]; + + // Verify protocol hash (accept 0 for backward compatibility, like CAN does) + if (frame_hash != avlos_proto_hash_8 && frame_hash != 0) + { + return; // Protocol hash mismatch + } + + // Validate endpoint ID + if (ep_id >= endpoint_count) + { + return; // Invalid endpoint + } + + // Calculate payload length + uint8_t payload_len = length - 4; // length - (Hash + EP_ID(2) + CMD) + + // Reject frames with invalid payload size (defense in depth) + if (payload_len > 8) + { + return; // Payload too large + } + + // Prepare buffer for endpoint call + uint8_t buffer[8] = {0}; + uint8_t buffer_len = payload_len; + + // Copy payload to buffer + if (payload_len > 0) + { + memcpy(buffer, &uart_rx_msg[UART_FRAME_HEADER_SIZE], payload_len); + } + + // Map command to Avlos command type + Avlos_Command avlos_cmd = (cmd == AVLOS_CMD_WRITE) ? AVLOS_CMD_WRITE : AVLOS_CMD_READ; + + // Call endpoint handler (same pattern as CAN) + uint8_t (*callback)(uint8_t buffer[], uint8_t *buffer_length, Avlos_Command cmd) = avlos_endpoints[ep_id]; + uint8_t response_type = callback(buffer, &buffer_len, avlos_cmd); + + // Send response if needed + if ((AVLOS_RET_READ == response_type || AVLOS_RET_CALL == response_type) && buffer_len > 0) + { + UART_send_response(ep_id, buffer, buffer_len); + } + + // Reset watchdog on successful message + Watchdog_reset(); } diff --git a/firmware/src/uart/uart_interface.h b/firmware/src/uart/uart_interface.h index b1b3d920..f4a76d41 100644 --- a/firmware/src/uart/uart_interface.h +++ b/firmware/src/uart/uart_interface.h @@ -19,9 +19,15 @@ #ifndef UART_UART_INTERFACE_H_ #define UART_UART_INTERFACE_H_ -#include "src/common.h" +#include +// UART Avlos Binary Protocol Constants +#define UART_SYNC_BYTE 0xAA // Frame start marker +#define UART_FRAME_HEADER_SIZE 6 // Sync + Length + Hash + EP_ID(2) + CMD +#define UART_FRAME_MIN_SIZE 8 // Header(6) + CRC(2), no payload +#define UART_FRAME_MAX_SIZE 16 // Header(6) + 8 bytes payload + CRC(2) + +// Process received UART message (called from scheduler) void UART_process_message(void); -void UART_SendInt32(int32_t val); #endif /* UART_UART_INTERFACE_H_ */ diff --git a/firmware/src/uart/uart_lowlevel.c b/firmware/src/uart/uart_lowlevel.c index 6179dd5f..9c675aea 100644 --- a/firmware/src/uart/uart_lowlevel.c +++ b/firmware/src/uart/uart_lowlevel.c @@ -17,36 +17,48 @@ #include #include -#include "src/uart/uart_func.h" +#include #include -char uart_rx_buf[96] = {0}; +// Receive buffer +uint8_t uart_rx_buf[UART_RX_BUFFER_SIZE] = {0}; uint8_t uart_rx_byte_idx = 0; +uint8_t uart_rx_msg[UART_RX_BUFFER_SIZE]; +uint8_t uart_rx_msg_len = 0; +// Transmit buffer +uint8_t uart_tx_msg[UART_TX_BUFFER_SIZE]; +uint8_t uart_tx_byte_idx = 0; +uint8_t uart_tx_frame_len = 0; + +// Receive state machine typedef enum { - MSG_TYPE_UNKNOWN = 0, - MSG_TYPE_ASCII = 1, - MSG_TYPE_BINARY = 2 -} SerialMessageType; + RX_STATE_IDLE = 0, // Waiting for sync byte + RX_STATE_LENGTH, // Waiting for length byte + RX_STATE_DATA // Receiving data bytes +} RxState; -SerialMessageType rx_msg_type = MSG_TYPE_UNKNOWN; +static RxState rx_state = RX_STATE_IDLE; +static uint8_t rx_expected_len = 0; -void ResetRxQueue(void) +static void ResetRxState(void) { uart_rx_byte_idx = 0; - rx_msg_type = MSG_TYPE_UNKNOWN; + rx_state = RX_STATE_IDLE; + rx_expected_len = 0; } -void ResetTxQueue(void) +static void ResetTxState(void) { uart_tx_byte_idx = 0; + uart_tx_frame_len = 0; } -void UART_Init() +void UART_Init(void) { uart_init(UART_ENUM, UART_BAUD_RATE); - ResetRxQueue(); - ResetTxQueue(); + ResetRxState(); + ResetTxState(); } void USARTB_IRQHandler(void) @@ -56,53 +68,82 @@ void USARTB_IRQHandler(void) if (int_type == UARTIIR_INTID_TX_HOLD_EMPTY) { - pac5xxx_uart_write2(UART_REF, uart_tx_msg[uart_tx_byte_idx]); - uart_tx_byte_idx++; - - // Terminate transmission upon newline or transmit overflow - if ((uart_tx_msg[uart_tx_byte_idx - 1u] == UART_LINEFEED) || - (uart_tx_byte_idx > UART_BYTE_LIMIT)) + // Transmit interrupt - send next byte + if (uart_tx_byte_idx < uart_tx_frame_len) + { + pac5xxx_uart_write2(UART_REF, uart_tx_msg[uart_tx_byte_idx]); + uart_tx_byte_idx++; + } + + // Check if transmission complete + if (uart_tx_byte_idx >= uart_tx_frame_len) { // Disable transmit interrupt pac5xxx_uart_int_enable_THREI2(UART_REF, UART_INT_DISABLE); - // Enable receive data interrupt for next incoming message - // pac5xxx_uart_int_enable_RDAI2(UART_REF, UART_INT_ENABLE); - ResetTxQueue(); + ResetTxState(); } } else - { - // Check first byte or return - if ((uart_rx_byte_idx == 0u) && (data == UART_ASCII_PROT_START_BYTE)) - { - rx_msg_type = MSG_TYPE_ASCII; - } - - if (rx_msg_type != MSG_TYPE_UNKNOWN) + { + // Receive interrupt - process incoming byte + switch (rx_state) { - uart_rx_buf[uart_rx_byte_idx] = data; - if ((rx_msg_type == MSG_TYPE_ASCII) && - (uart_rx_buf[uart_rx_byte_idx] == UART_LINEFEED)) - { - uart_rx_msg_len = uart_rx_byte_idx + 1u; - memcpy(&uart_rx_msg, &uart_rx_buf, uart_rx_msg_len); - ResetRxQueue(); - UART_ReceiveMessageHandler(); - // Disable receive data interrupt - //pac5xxx_uart_int_enable_RDAI2(UART_REF, UART_INT_DISABLE); - // Reset RX FIFO, to clear RDAI interrupt - pac5xxx_uart_rx_fifo_reset2(UART_REF); - - } - else if (uart_rx_byte_idx >= UART_BYTE_LIMIT) - { - ResetRxQueue(); - } - else - { + case RX_STATE_IDLE: + // Looking for sync byte + if (data == UART_SYNC_BYTE_LL) + { + uart_rx_buf[0] = data; + uart_rx_byte_idx = 1; + rx_state = RX_STATE_LENGTH; + } + break; + + case RX_STATE_LENGTH: + // Got length byte + uart_rx_buf[1] = data; + uart_rx_byte_idx = 2; + // Total frame length = Sync(1) + Length(1) + data(length) + CRC(2) + rx_expected_len = 2 + data + 2; + + // Validate length + if (rx_expected_len > UART_RX_BUFFER_SIZE || data < 4) + { + // Invalid length, reset + ResetRxState(); + } + else + { + rx_state = RX_STATE_DATA; + } + break; + + case RX_STATE_DATA: + // Receiving data bytes + uart_rx_buf[uart_rx_byte_idx] = data; uart_rx_byte_idx++; - } - + + // Check if frame complete + if (uart_rx_byte_idx >= rx_expected_len) + { + // Copy to message buffer and signal handler + uart_rx_msg_len = uart_rx_byte_idx; + memcpy(uart_rx_msg, uart_rx_buf, uart_rx_msg_len); + ResetRxState(); + UART_ReceiveMessageHandler(); + + // Reset RX FIFO + pac5xxx_uart_rx_fifo_reset2(UART_REF); + } + else if (uart_rx_byte_idx >= UART_RX_BUFFER_SIZE) + { + // Buffer overflow, reset + ResetRxState(); + } + break; + + default: + ResetRxState(); + break; } } } diff --git a/firmware/src/uart/uart_lowlevel.h b/firmware/src/uart/uart_lowlevel.h index 9b9552ca..5b62d09b 100644 --- a/firmware/src/uart/uart_lowlevel.h +++ b/firmware/src/uart/uart_lowlevel.h @@ -18,22 +18,26 @@ #ifndef UART_UART_LOWLEVEL_H_ #define UART_UART_LOWLEVEL_H_ -#define UART_LINEFEED 0x0A -#define UART_CRETURN 0x0D -#define UART_ASCII_PROT_START_BYTE 0x2E -#define UART_BYTE_LIMIT 32 +#include -char uart_rx_msg[96]; -uint8_t uart_rx_msg_len; +// UART Protocol Constants +#define UART_SYNC_BYTE_LL 0xAA // Binary protocol sync byte +#define UART_RX_BUFFER_SIZE 32 // Max receive buffer size +#define UART_TX_BUFFER_SIZE 32 // Max transmit buffer size -char uart_tx_msg[96]; -uint8_t uart_tx_byte_idx; +// Receive buffer and state +extern uint8_t uart_rx_msg[UART_RX_BUFFER_SIZE]; +extern uint8_t uart_rx_msg_len; +// Transmit buffer and state +extern uint8_t uart_tx_msg[UART_TX_BUFFER_SIZE]; +extern uint8_t uart_tx_byte_idx; +extern uint8_t uart_tx_frame_len; + +// Initialize UART peripheral void UART_Init(void); -// The following message handler is called when a UART -// message completes being received i.e. with a newline -// character +// Message handler callback (called when a complete frame is received) extern void UART_ReceiveMessageHandler(void); #endif /* UART_UART_LOWLEVEL_H_ */