STM32 Arduino Core: Unexpected double CRLF behavior in Serial.println vs SAMD51

Summary

  • STM32Duino core produces double CRLF (\r\n\r\n) for string constants in Serial.println() while numeric values output correctly.
  • SAMD51 core behaves as expected with single CRLF (\r\n) for both cases.
  • Root cause stems from HAL-level CRLF conversion conflicting with Arduino’s standard newline handling.

Root Cause

  • STM32 HAL UART drivers automatically convert \n to \r\n during transmission.
  • Arduino’s println() appends \r\n to output.
  • For strings: The HAL converts embedded \n (if any) to \r\n, then println() appends another \r\ndouble CRLF.
  • For numeric values: No embedded \n exists, so only println()‘s \r\n is transmitted → single CRLF.

Why This Happens in Real Systems

  • Hardware abstraction layers (HALs) vary across microcontrollers, leading to inconsistent behavior when porting code.
  • Arduino Print class assumes raw data, but STM32’s HAL applies transformations.
  • Core-specific overrides (e.g., Serial.write()) can introduce unintended side effects.
  • PlatformIO/Arduino core mismatches expose hidden HAL inconsistencies.

Real-World Impact

  • Protocol violations in systems expecting single CRLF (e.g., Modbus, custom protocols).
  • Log parsing failures in tools like Wireshark or serial monitors.
  • Buffer overflows if output pipelines assume fixed-length line endings.
  • Debugging nightmares due to silent data corruption.

Example or Code

// Minimal code to reproduce the issue
void setup() {
  Serial.begin(115200);

  // Numeric value (correct output: "10\r\n")
  Serial.println(10); 

  // String constant (problematic output: "Hello\r\n\r\n")
  Serial.println("Hello");

  // Force single CRLF workaround
  Serial.print("Test");
  Serial.write('\r'); Serial.write('\n'); // Avoid println()
}

void loop() {}

How Senior Engineers Fix It

  1. Disable HAL-level CRLF conversion by modifying core files:
    • Locate HardwareSerial.cpp in the STM32Duino core.
    • Comment out HAL_UART_Transmit_IT(&huart, (uint8_t*)"\r\n", 2) in write() calls.
  2. Use direct HAL functions to bypass Arduino’s Print layer:
    // Replace Serial.println("ERROR")
    char msg[] = "ERROR";
    HAL_UART_Transmit(&huart, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
    HAL_UART_Transmit(&huart, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
  3. Configure UART peripheral in initialization to disable auto-CRLF:
    huart.Init.Parity = UART_PARITY_NONE;
    huart.Init.StopBits = UART_STOPBITS_1;
    huart.Init.Mode = UART_MODE_TX_RX;
    huart.Init.WordLength = UART_WORDLENGTH_8B;

Why Juniors Miss It

  • Assume Arduino cores are identical, overlooking HAL differences.
  • Debug via serial monitors that auto-display \r\n as a single newline, masking the extra \r.
  • Lack familiarity with HAL implementation details causing unexpected data transformations.
  • Focus on application logic, not low-level peripheral behavior.

Leave a Comment