Skip to content

UART/RS-232 Communication Module

Overview

This document describes the UART (serial) communication subsystem used for RS-232 style communication with the motor inverter. The implementation uses DMA with circular buffering and Idle Line detection for efficient, non-blocking data reception.

File Locations

  • Headers: Core/Inc/App/Communication/uart_comm.h
  • Source: Core/Src/App/Communication/uart_comm.c

Physical Layer Configuration

  • Interface: UART4 (STM32L562xx)
  • Baud Rate: 115200 baud (configurable)
  • Data Format: 8 bits, no parity, 1 stop bit (8N1)
  • Transmission Delay: 25ms (UART_DELAY constant)

Reception Mechanism: DMA with Circular Buffering

Circular Buffer Setup

  • Buffer Size: 2048 bytes (UART_BUFFER_SIZE)
  • Architecture: Circular ring buffer managed by DMA
  • Memory Location: inverter_data.Buffer.rx_buffer

The circular buffer allows continuous data reception without frame boundaries or buffer overflow concerns:

c
typedef struct {
    uint16_t old_pos;           // Previous processing position
    uint16_t current_pos;       // Current DMA position (updated by Idle Line interrupt)
    uint8_t new_uart_data_flag; // Flag set when new data is available
    char rx_buffer[UART_BUFFER_SIZE];
} Buffer_t;

Circular Buffer State Diagram

Circular Buffer Memory Layout

Buffer (2048 bytes)
┌─────────────────────────────────────────────────────────┐
│ 0x00  ...  old_pos ...  current_pos ...  0x800         │
│                    ↓                  ↓                 │
│          [PROCESSED]    [NEW DATA]    [EMPTY]          │
│                                       ↑ Next DMA write│
└─────────────────────────────────────────────────────────┘
                            ↓ Wraps around
         ┌──────────────────┘

         └→ old_pos = 0x000
            current_pos wraps to buffer start

Diagram showing wraparound handling:

Idle Line Detection

The UART peripheral monitors the receive line and triggers an interrupt when the line remains idle (no transitions) for a character period. This mechanism eliminates the need for explicit frame delimiters:

Trigger Events:

  • HAL_UART_RXEVENT_IDLE - Line idle for one character duration (11 bits @ 115200 baud ≈ 95 µs)
  • HAL_UART_RXEVENT_TC - DMA Transfer Complete (buffer full)

Idle Line Detection Timing:

Idle Line State Diagram:

Callback Implementation:

c
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){
    if (huart->Instance == UART4) {
        if (huart->RxEventType == HAL_UART_RXEVENT_TC || 
            huart->RxEventType == HAL_UART_RXEVENT_IDLE) {
            // Update current position in circular buffer
            inverter_data.Buffer.current_pos = Size % UART_BUFFER_SIZE;
            // Signal that new data is available for processing
            inverter_data.Buffer.new_uart_data_flag = 1;
        }
    }
}

Advantages of Idle Line Detection:

  • No need for start/stop markers
  • Automatically detects complete messages
  • Tolerant to variable message lengths
  • Low CPU overhead (interrupt-driven)
  • Handles network traffic bursts efficiently

Message Reception Window Processing

Data is processed in "windows" defined by the buffer positions:

c
void inverter_uart_poll_rx(uint8_t *rx_buffer) {
    if (inverter_data.Buffer.new_uart_data_flag == 0) {
        return;  // No new data
    }
    
    // Process data from old_pos to current_pos
    process_uart_rx_window(rx_buffer, 
                          inverter_data.Buffer.old_pos, 
                          inverter_data.Buffer.current_pos);
    
    // Update position for next window
    inverter_data.Buffer.old_pos = inverter_data.Buffer.current_pos;
    inverter_data.Buffer.new_uart_data_flag = 0;
}

Window Extraction:

  • Handles circular buffer wraparound correctly
  • Extracts data between old and current positions
  • Implements overflow protection (discards oldest data if destination buffer is too small)
  • Null-terminates extracted data for string parsing

Message Protocol

Inverter Data Format (Received)

The inverter sends telemetry data as key=value pairs, one per line, delimited by CR/LF:

S=2.45
a=0.12
PWM=45
U=48.2
I=15.3
RPM=2450
con=42
mot=35

Example Message Extraction Sequence:

Supported Fields:

  • S, T, t - Throttle/Speed input (Volts)
  • a, A - Auxiliary/Brake input (Volts)
  • PWM - Pulse Width Modulation value (%)
  • U - Input voltage (Volts)
  • I - Phase current (Amperes)
  • RPM - Rotational speed (revolutions per minute)
  • con - Motor controller temperature (°C)
  • mot - Motor temperature (°C)

Parsing

Messages are parsed line-by-line using standard C string functions:

  • Lines are extracted between CR/LF delimiters
  • Each line containing = is parsed as a key=value pair
  • Values are extracted using sscanf() and strstr()
  • Unknown fields are silently ignored
c
// Parse throttle (can be S, T, or t depending on inverter mode)
if ((ptr = strstr(temp_msg, "S=")) != NULL)
    sscanf(ptr, "S=%f", &inverter_data.RS232_Message.S);
else if ((ptr = strstr(temp_msg, "T=")) != NULL)
    sscanf(ptr, "T=%f", &inverter_data.RS232_Message.S);

Command Transmission

Commands are sent to the inverter in two modes:

Normal Mode (Speed Control)

Sends speed control commands using a compact encoding:

c
void inverter_uart_send_command(Inverter_Data_t *inverter_data, 
                               uint8_t programming_mode, 
                               uint8_t force_send)

Speed Encoding Examples:

Speed Command Reference Table:

Speed (%)CommandDescription
0%0No movement
1-9%s1- to s1---------1% to 9% (1 dash per %)
10%s10Base 10%
11-19%s10+ to s10++++++++++10% + adjustment
20-99%s0X + adjTens digit + dashes/plus
100%mFull power

Example Transmissions:

c
// Example 1: 45% speed (s04++)
inverter_data.left_percentage = 45;
inverter_uart_send_command(&inverter_data, 0, 0);
// Sends: "s04++" (4 from tens, ++ adds 5)

// Example 2: 8% speed (s1--------)
inverter_data.right_percentage = 8;
inverter_uart_send_command(&inverter_data, 0, 0);
// Sends: "s1--------" (8 dashes = 8%)

// Example 3: 100% speed (m)
inverter_data.left_percentage = 100;
inverter_uart_send_command(&inverter_data, 0, 0);
// Sends: "m"

Transmission Timing:

Programming Mode (Pass-through)

In programming mode, raw data from CAN is forwarded directly to the inverter. Up to 14 bytes can be sent per transmission.

RS-232 Switch Command

c
void inverter_uart_switch_to_rs232_mode(void) {
    HAL_UART_Transmit(&huart4, (uint8_t*)"s", 1, UART_DELAY);
}

Sends a simple s character to switch inverter to RS-232 communication mode.

Non-Programming Command Period

  • Period: 3ms (UART_NON_PROG_TX_PERIOD_MS)
  • Controls update rate of speed commands in normal operation
  • Only sends if speed percentage has changed

Data Structure

c
typedef struct {
    struct {
        float S;           // Throttle input (Volts)
        float a;           // Aux/Brake input (Volts)
        uint16_t PWM;      // PWM percentage
        float U;           // Input voltage (Volts)
        float I;           // Phase current (Amperes)
        uint16_t RPM;      // Rotational speed
        uint8_t con;       // Controller temperature (°C)
        int8_t mot;        // Motor temperature (°C)
    } RS232_Message;
    
    struct {
        uint16_t old_pos;
        uint16_t current_pos;
        uint8_t new_uart_data_flag;
        uint8_t previous_percentage;
        char rx_buffer[UART_BUFFER_SIZE];
    } Buffer;
    
    char raw_data_tx[15];
    uint8_t left_percentage, right_percentage;
    Programming_Message_t prog_message;
    Inverter_Message_t inv_message;
} Inverter_Data_t;

Error Handling

Error Recovery Flow:

Error Handling Details:

  • Null Buffer Detection: Detects and flags null pointer access in circular buffer operations
  • Buffer Overflow Protection: Older data is discarded if incoming message exceeds destination buffer size
  • Malformed Messages: Lines without = are silently ignored
  • Parsing Failures: Missing or invalid fields retain previous values

Integration Notes

  • Polling function inverter_uart_poll_rx() should be called periodically from the main task loop
  • Idle Line interrupt must be enabled in the HAL UART configuration
  • DMA must be configured for continuous circular mode
  • Buffer is shared with programming module for pass-through functionality
  • UART4 handle (huart4) must be properly initialized by STM32CubeMX

Key Takeaways

  • Non-blocking reception through DMA circular buffering
  • Automatic frame detection using Idle Line interrupt (~95 µs threshold)
  • Simple protocol with key=value pairs and standard line delimiters
  • Compact speed encoding minimizes message overhead
  • Resilient parsing that handles missing or invalid fields gracefully
  • Pass-through capable for remote diagnostics and over-the-air programming

Released under the MIT License.