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\n → double 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
- 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.
- 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);
- 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.