๐ English Abstract
This technical article provides an in-depth troubleshooting guide for connection drops during high-speed data transmission using the STM32 USB Device CDC protocol. It explains the root cause of Packet Memory Area (PMA) buffer underrun and demonstrates how to resolve host-side timeouts and packet loss. Furthermore, it details the step-by-step implementation of double buffering and ring buffer management to achieve maximum USB Full-Speed throughput and system stability.
1. ์๋ก : STM32 USB CDC ๊ณ ์ ๋ฐ์ดํฐ ์ ์ก ์ ๋ฐ์ํ๋ ์ฐ๊ฒฐ ๋๊น ํ์
STM32 ์๋ฒ ๋๋ ์์คํ ์์ USB CDC(Communication Device Class) ํ๋กํ ์ฝ์ ๊ฐ์ ์ปดํฌํธ(Virtual COM Port, VCP)๋ฅผ ๊ตฌํํ์ฌ PC์ ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ ๊ฐ ๊ณ ์ ๋ฐ์ดํฐ ํต์ ์ ์ํํ ๋ ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ฐฉ์์ ๋๋ค. ๊ทธ๋ฌ๋ ์ผ์ ๋ฐ์ดํฐ ์คํธ๋ฆฌ๋ฐ, ๊ณ ํด์๋ ๋ก๊น , ๋ฐ์ดํฐ ์์ง ์ฅ์น(DAQ) ๋ฑ ์ฐ์์ ์ธ ๊ณ ์ ๋ฐ์ดํฐ(High-Throughput)๋ฅผ ์ ์กํ๋ ์ค๋ฌด ํ์ฅ์์ ์์ธ์ ์ ์ ์๋ **USB ์ฐ๊ฒฐ ๋๊น(Disconnection)**, **ํจํท ๋๋ฝ(Packet Loss)**, ๋๋ **ํธ์คํธ ๋๋ผ์ด๋ฒ ๋จนํต ํ์**์ ์์ฃผ ๊ฒช๊ฒ ๋ฉ๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ ๋๊ฐ ๋จ์ํ C์ธ์ด ์ฝ๋ ์ค๋ฅ๊ฐ ์๋, STM32 ๋ด๋ถ **USB ์ฃผ๋ณ์ฅ์น(Peripheral)์ ํ๋์จ์ด ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์กฐ**์ **HAL ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ํํธ์จ์ด ํ์ด๋ฐ ๊ฐ ๋ถ์ผ์น**์์ ๋ฐ์ํฉ๋๋ค. ํนํ USB ์ปจํธ๋กค๋ฌ๊ฐ ์ง์ ์ ์ฉ SRAM์ ์ ๊ทผํ๋ ์์ญ์ธ **PMA(Packet Memory Area) ๋ฒํผ์ ์ธ๋๋ฐ(Underrun) ๋ฐ ์ค๋ฒ๋ฐ(Overrun)** ํ์์ด ์ฃผ์ ์์ธ์ ๋๋ค.
๋ณธ ํฌ์คํ ์์๋ STM32 USB CDC ํต์ ์ค ๋์ฉ๋ ์ ์ก ์ ๋ฐ์ํ๋ ๋ฒํผ ์ธ๋๋ฐ ํ์์ ๋ฉ์ปค๋์ฆ์ ํ๋์จ์ด ๋ ๋ฒจ์์ ๋ถ์ํ๊ณ , ์ด๋ฅผ ๊ทผ๋ณธ์ ์ผ๋ก ํด๊ฒฐํ ์ ์๋ **์ด์ค ๋ฒํผ๋ง(Double Buffering)** ๋ฐ **์ํ ๋ฒํผ(Ring Buffer) ์ต์ ํ ํธ๋ฌ๋ธ์ํ ** ๊ธฐ๋ฒ์ ๋ค๋ฃน๋๋ค.
2. ์์ธ ๋ถ์: PMA(Packet Memory Area) ๊ตฌ์กฐ์ ์ธ๋๋ฐ ๋ฉ์ปค๋์ฆ
2.1. PMA(Packet Memory Area) ๋ฉ๋ชจ๋ฆฌ ๋งคํ ๊ตฌ์กฐ์ ์ดํด
STM32 MCU(์: STM32F1, F3, L4 ์๋ฆฌ์ฆ ๋ฑ)์ USB ํ๋์จ์ด๋ ์ผ๋ฐ SRAM๊ณผ ๋ถ๋ฆฌ๋ **PMA(Packet Memory Area)**๋ผ๋ ์ ์ฉ ํจํท ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ ๊ฐ์ต๋๋ค. USB ์ฃผ๋ณ์ฅ์น ์ปจํธ๋กค๋ฌ๋ ํธ์คํธ(PC)์ ํจํท์ ์ฃผ๊ณ ๋ฐ์ ๋ ์ด PMA๋ฅผ ์ง์ ์ฝ๊ฑฐ๋ ์๋๋ค.
- ๋ฒํผ ๋์คํฌ๋ฆฝํฐ ํ ์ด๋ธ (Buffer Descriptor Table): PMA์ ์ต์๋จ์ ์์นํ๋ฉฐ, ๊ฐ ์๋ํฌ์ธํธ(Endpoint)์ TX/RX ๋ฒํผ ์์ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ๊ธธ์ด๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
- SRAM ์ ๊ทผ ์ ์ฝ ์ฌํญ: ์ผ๋ถ STM32 ํจ๋ฐ๋ฆฌ์์๋ PMA ๋ฉ๋ชจ๋ฆฌ๊ฐ 16๋นํธ ๋จ์ ๋๋ 32๋นํธ ๋จ์๋ก ์ต์ธ์ค ์ ํ์ด ์์ผ๋ฉฐ, ์์คํ CPU ๋ฉ๋ชจ๋ฆฌ(SRAM)์์ PMA๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ ๋ ํ๋์จ์ด ๋ ๋ฒจ์ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํฉ๋๋ค.
2.2. Single Buffer ํ๊ฒฝ์์ PMA Buffer Underrun์ด ๋ฐ์ํ๋ ์ด์
STM32CubeMX๋ก ์๋ ์์ฑ๋ ๊ธฐ๋ณธ CDC ์ฝ๋(Class Driver)๋ ๋๋ถ๋ถ **๋จ์ผ ๋ฒํผ๋ง(Single Buffering)** ๊ตฌ์กฐ๋ก ๋์ํฉ๋๋ค. ๋ฐ์ดํฐ ์ ์ก ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ ํ๋ฆฌ์ผ์ด์
์์ญ์์
USBD_CDC_TransmitPacket()ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. - ๋ด๋ถ
CDC_Itf_Transmit()๊ฐUSBD_CDC_SetTxBuffer()๋ฅผ ํตํด ์ ์ก ๋ฒํผ ์ฃผ์๋ฅผ ์ง์ ํฉ๋๋ค. - USB ์ฃผ๋ณ์ฅ์น๊ฐ IN ์๋ํฌ์ธํธ ์์ฒญ(IN Token)์ ํธ์คํธ๋ก๋ถํฐ ์์ ํ๋ฉด PMA์ ๋ฐ์ดํฐ๋ฅผ PHY ๋ผ์ธ์ผ๋ก ๋ด๋ณด๋ ๋๋ค.
- ์ ์ก์ด ์๋ฃ๋๋ฉด
USBD_CDC_DataIn()์ฝ๋ฐฑ ์ธํฐ๋ฝํธ๊ฐ ๋ฐ์ํ์ฌTxState๊ฐ IDLE(0) ์ํ๋ก ๋ณต๊ทํฉ๋๋ค.
๋ฌธ์ ๋ฐ์ ์ง์ : ํธ์คํธ PC๊ฐ 1ms ๋ง๋ค(Full-Speed ๊ธฐ์ค Frame ๋จ์) IN ํ ํฐ์ ์ก์ ํ ๋, MCU ์ธก์์ USBD_CDC_TransmitPacket()์ ํตํด ๋ค์ ํจํท์ PMA์ ๋ค ์ฑ์ฐ๊ธฐ๋ ์ ์ IN ํ ํฐ์ด ๋ค์ด์ค๋ฉด, USB ํ๋์จ์ด๋ ๋ณด๋ด์ค ๋ฐ์ดํฐ๊ฐ ์์ด **NAK(Negative Acknowledgment)** ํจํท์ ๋ฐํํฉ๋๋ค.
๋ง์ฝ ์ฐ์์ ์ธ ๊ณ ์ ์ ์ก ์ํฉ์์ CPU๊ฐ ๋ค๋ฅธ ์ฐ์ ์์ ๋์ ์ธํฐ๋ฝํธ(์: DMA, TIM, UART ISR)๋ฅผ ์ฒ๋ฆฌํ๋๋ผ USBD_CDC_TransmitPacket() ํธ์ถ ํ์ด๋ฐ์ ๋์น๊ฑฐ๋, TxState๊ฐ BUSY ์ํ์ผ ๋ ๊ณ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ด ๋ฃ์ผ๋ฉด, ๋ฐ์ดํฐ๊ฐ ๋ฎ์ด์์์ง๊ฑฐ๋ PMA ์ ์ก ํ์ด๋ฐ์ด ๋คํ๋ฆฌ๋ฉด์ **PMA Buffer Underrun / Overrun**์ด ๋ฐ์ํฉ๋๋ค. ๊ฒฐ๊ตญ ํธ์คํธ ์๋ต ๋๊ธฐ ์๊ฐ์ด ์ด๊ณผ๋์ด USB ์ ์ด ์ ํธ๊ฐ ๋ฆฌ์
(Bus Reset)๋๋ฉด์ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์ฐ๊ฒฐ์ด ๋์ด์ง๋ ๊ฒฐ๊ณผ๋ก ์ด์ด์ง๋๋ค.
3. ํด๊ฒฐ์ฑ 1: ์ด์ค ๋ฒํผ๋ง(Double Buffering) ๋ฉ์ปค๋์ฆ ๊ตฌ์ถ
3.1. ์ด์ค ๋ฒํผ๋ง(Ping-Pong Buffer)์ ๋์ ์๋ฆฌ
๋จ์ผ ๋ฒํผ ๊ตฌ์กฐ์ ํ๊ณ๋ฅผ ๊ทน๋ณตํ๊ธฐ ์ํด **์ด์ค ๋ฒํผ๋ง(Double Buffering / Ping-Pong Buffering)** ๊ธฐ์ ์ ์ฌ์ฉํฉ๋๋ค. ์ด ๊ตฌ์กฐ์์๋ ํ๋์จ์ด๊ฐ Buffer 0์์ ํจํท์ USB ๋ผ์ธ์ผ๋ก ์ ์กํ๋ ๋์, CPU๋ Buffer 1์ ๋ค์ ์ ์กํ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ์ค๋น(Write)ํ ์ ์์ต๋๋ค.
| ๋จ๊ณ (Time Slot) | USB Hardware (PMA Send) | CPU / Application (Buffer Write) |
|---|---|---|
| Slot N | Buffer 0 ์ฝ์ด์ PC๋ก ์ ์ก | Buffer 1์ ๋ค์ ๋ฐ์ดํฐ ์ฑ์ฐ๊ธฐ |
| Slot N+1 | Buffer 1 ์ฝ์ด์ PC๋ก ์ ์ก | Buffer 0์ ๋ค์ ๋ฐ์ดํฐ ์ฑ์ฐ๊ธฐ |
์ด ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด USB ๋ผ์ธ์ด Idle ์ํ์ ๋น ์ ธ NAK์ ๋จ๋ฐํ๋ ํ์์ ๋ํญ ์ค์ผ ์ ์์ผ๋ฉฐ, CPU ์ธํฐ๋ฝํธ ์ง์ฐ์๊ฐ(Interrupt Latency)์ ๋ํ ๋ง์ง์ ํ๋ณดํ ์ ์์ต๋๋ค.
4. ์ค๋ฌด ์ฝ๋ ์ต์ ํ: ๋ง ๋ฒํผ(Ring Buffer) ๋ฐ Non-Blocking ์ค๊ณ
์ด์ค ๋ฒํผ๋ง ๊ธฐ๋ฒ์ ์์ ์ ์ผ๋ก ์ด์ฉํ๊ธฐ ์ํด์๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ญ๊ณผ USB ๋๋ผ์ด๋ฒ ๊ณ์ธต ์ฌ์ด์ **์ ์ ์ ์ถ(FIFO) ๋ฐฉ์์ ์ํ ๋ง ๋ฒํผ(Ring Buffer)**๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ํตํด CPU๊ฐ ๋ฉ์ธ ๋ฃจํ์์ ๋น ๋ฅด๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ฒํผ๋งํ๊ณ , USB ISR(Interrupt Service Routine)์ ๋น๋๊ธฐ์ ์ผ๋ก PMA๋ก ๋ฐ์ดํฐ๋ฅผ ํผ๋๋ฅด๋๋ก ๊ตฌํํฉ๋๋ค.
4.1. ์ค๋ ๋ ์ธ์ดํ(Thread-Safe) ๋ง ๋ฒํผ ๊ตฌํ
์๋ C์ธ์ด ์ฝ๋๋ CDC ํต์ ์ ๋ฐ์ดํฐ ์ ์ค ๋ฐ ์ธ๋๋ฐ์ ๋ฐฉ์งํ๊ธฐ ์ํ ๋์ฉ๋ Ring Buffer ๊ตฌ์กฐ์ฒด ๋ฐ ์ ์ก ํจ์ ์์์ ๋๋ค.
#define CDC_RING_BUFFER_SIZE 4096 // ์ถฉ๋ถํ ํฌ๊ธฐ์ ๋ง๋ฒํผ ํ ๋น (2์ ๊ฑฐ๋ญ์ ๊ณฑ ๊ถ์ฅ)
#define USB_MAX_PACKET_SIZE 64 // USB FS CDC Max Packet Size
typedef struct {
uint8_t buffer[CDC_RING_BUFFER_SIZE];
volatile uint32_t head;
volatile uint32_t tail;
} USB_RingBuffer_t;
static USB_RingBuffer_t cdc_tx_ring;
// ๋ง ๋ฒํผ ๋ฐ์ดํฐ ํธ์ ํจ์
bool CDC_RingBuffer_Write(const uint8_t *pData, uint32_t length) {
for (uint32_t i = 0; i < length; i++) {
uint32_t next_head = (cdc_tx_ring.head + 1) % CDC_RING_BUFFER_SIZE;
if (next_head == cdc_tx_ring.tail) {
return false; // Buffer Full (์ค๋ฒํ๋ก์ฐ ๋ฐฉ์ง)
}
cdc_tx_ring.buffer[cdc_tx_ring.head] = pData[i];
cdc_tx_ring.head = next_head;
}
return true;
}
// USB TX ์ธํฐ๋ฝํธ ์ฒ๋ฆฌ ๋ฐ ์ฐ์ ์ ์ก ํธ๋ฆฌ๊ฑฐ ํจ์
void CDC_Continue_Transmit(void) {
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) hUsbDeviceFS.pClassData;
// USB ๊ฐ ํ์ฌ ์ ์ก ๋ถ๊ฐ๋ฅ ์ํ(BUSY)์ด๋ฉด ๋ฆฌํด
if (hcdc->TxState != 0) {
return;
}
uint32_t fill_size = 0;
static uint8_t temp_packet[USB_MAX_PACKET_SIZE];
// ๋ง ๋ฒํผ์์ ๋ฐ์ดํฐ ๊บผ๋ด๊ธฐ
while (cdc_tx_ring.tail != cdc_tx_ring.head && fill_size < USB_MAX_PACKET_SIZE) {
temp_packet[fill_size++] = cdc_tx_ring.buffer[cdc_tx_ring.tail];
cdc_tx_ring.tail = (cdc_tx_ring.tail + 1) % CDC_RING_BUFFER_SIZE;
}
if (fill_size > 0) {
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, temp_packet, fill_size);
USBD_CDC_TransmitPacket(&hUsbDeviceFS);
}
}
4.2. usbd_cdc_if.c ์ฝ๋ฐฑ ํจ์ ์์
USB ์ ์ก์ด ์๋ฃ๋์์ ๋ ํธ์ถ๋๋ ์ฝ๋ฐฑ ํจ์์ธ CDC_TransmitCplt_FS() ๋ด๋ถ์์ ๋ง๋ฒํผ์ ๋จ์์๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ๋ณต์ฌํ์ฌ ํจํท์ ์ฌ์ ์กํ๋๋ก ์ฐ๊ฒฐํด์ผ ํฉ๋๋ค.
static int8_t CDC_TransmitCplt_FS(uint8_t *Buf, uint32_t *Len, uint8_t epnum) {
uint8_t result = USBD_OK;
// ์ด์ ์ ์ก์ ์ค์ด๋ ์ธํฐ๋ฝํธ ์๋ฃ ํ ์ฐ์ ๋ฐ์ดํฐ ์ก์
CDC_Continue_Transmit();
return (int8_t)result;
}
5. ๋๋ฒ๊น ๋ฐ ํธ๋ฌ๋ธ์ํ ์ฃ์ง ์ผ์ด์ค (Edge Cases)
5.1. Zero-Length Packet (ZLP) ์ฒ๋ฆฌ ์ด์
USB ํต์ ๊ท์ฝ์, ์ ์กํ๋ ค๋ ๋ฐ์ดํฐ ํจํท์ ํฌ๊ธฐ๊ฐ ์ ํํ Endpoint์ ์ต๋ ํฌ๊ธฐ์ธ 64๋ฐ์ดํธ์ ๋ฐฐ์์ผ ๊ฒฝ์ฐ, ํธ์คํธ PC๋ ๋ฐ์ดํฐ ์ ์ก์ด ์์ง ๋๋์ง ์์ ๊ฒ์ผ๋ก ๊ฐ์ฃผํ๊ณ ์์ ๋๊ธฐ ์ํ์ ๋น ์ง๋๋ค.
- ์ฆ์: PC ์ธก ์์ ํ๋ก๊ทธ๋จ์ด 64, 128, 256 ๋ฐ์ดํธ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๋ ์๋ต์ ๋ฉ์ถ๊ฑฐ๋ ํ์์์ ๋ฐ์.
- ํด๊ฒฐ์ฑ : ์ ์ก ๋ฐ์ดํฐ ๊ธธ์ด๊ฐ 64๋ฐ์ดํธ์ ๋ฐฐ์์ผ ๊ฒฝ์ฐ ๊ธธ์ด๊ฐ 0์ธ **ZLP(Zero-Length Packet)**๋ฅผ ๋ช ์์ ์ผ๋ก ์ถ๊ฐ ์ก์ ํ์ฌ ํธ๋์ญ์ ์ข ๊ฒฐ์ ํธ์คํธ์ ์๋ ค์ผ ํฉ๋๋ค.
5.2. Priority Inversion (์ฐ์ ์์ ์ญ์ ) ๋ฐ ISR ์ง์ฐ
STM32์ USB Interrupt ์ฐ์ ์์๊ฐ ๋ค๋ฅธ ์ฃผ๋ณ์ฅ์น(์: Systick, FreeRTOS Kernel Tick, USART ISR)๋ณด๋ค ๋ฎ๊ฒ ์ค์ ๋์ด ์์ผ๋ฉด, USB ์ ์ก ์๋ฃ ์ธํฐ๋ฝํธ๊ฐ ์ง์ฐ๋์ด PMA ๋ฒํผ๊ฐ ๋น์ด์๋ ์ฑ๋ก ์ฑ์์ง์ง ์๋ ํ์์ด ์๊น๋๋ค.
- NVIC ์ค์ ์ **USB Interrupt Priority**๋ฅผ ์์คํ ๋ด์์ ์๋์ ์ผ๋ก ๋๊ฒ ์ค์ (์ซ์๋ฅผ ๋ฎ๊ฒ ์กฐ์ )ํ์ฌ ์ธํฐ๋ฝํธ ์๋ต ์๋๋ฅผ ์ต์ ํํด์ผ ํฉ๋๋ค.
6. ๊ฒฐ๋ก ๋ฐ ๊ณ ์ฑ๋ฅ USB CDC ์ค๊ณ๋ฅผ ์ํ ์ฒดํฌ๋ฆฌ์คํธ
STM32 MCU ๊ธฐ๋ฐ์ USB CDC ํ๋กํ ์ฝ์ ๊ตฌํํ ๋ ๊ณ ์ ์ ์ก ์ค ๋๊น ํ์์ ๋ฐฉ์งํ๊ธฐ ์ํ ํต์ฌ ์์ฝ ์ฒดํฌ๋ฆฌ์คํธ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- PMA ๋ฒํผ์ ์ง์ ์ ์ฐ์ ์ฐ๊ธฐ ๊ธ์ง:
USBD_CDC_TransmitPacket()์ด ์๋ฃ(IDLE)๋์ง ์์ ์ํ์์ ์ฐ์ ํธ์ถ์ ์ง์ํ๊ณ , Intermediate **Ring Buffer**๋ฅผ ๊ฒฝ๊ตฌํ์ฌ ์ ์ดํ ๊ฒ. - ์ด์ค ๋ฒํผ๋ง(Double Buffering) ํ์ฑํ: ํ๋์จ์ด ๋ ๋ฒจ ์ง์ MCU์ ๊ฒฝ์ฐ Double Buffer EP ๋ชจ๋๋ฅผ ์ค์ ํ์ฌ PMA ํ ๋น ๋ง์ง์ ๋ ๋ฐฐ๋ก ๋๋ฆด ๊ฒ.
- NVIC ์ฐ์ ์์ ์กฐ์ : USB ํธ๋ค๋ฌ ์ธํฐ๋ฝํธ๊ฐ ํ ์ง์ฐ ์์(Timers, External Interrupts)์ ๋ฐ๋ ค PMA ์ฑ์ฐ๊ธฐ๊ฐ ๋ฆ์ด์ง์ง ์๋๋ก ์กฐ์ ํ ๊ฒ.
- ZLP ์ฒ๋ฆฌ ์ฒดํฌ: 64๋ฐ์ดํธ ๋จํธํ ํจํท ์ ์ก ์ ๋ง์ง๋ง ์ ์ด ์ ๋ก ํจํท ์ฒ๋ฆฌ๋ฅผ ๋ช ํํ ํฌํจํ ๊ฒ.
์์ ๊ฐ์ด ๋์ฉ๋ ๋ง ๋ฒํผ๋ง๊ณผ ๋น๋๊ธฐ ISR ์ฒ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ํ๋ฆฝํ๋ฉด, STM32 USB CDC ์ฌ์ฉ ์ ํ์คํผ๋(Full-Speed) ๋์ญํญ์ธ **12Mbps(์คํจ ์๋ ์ฝ 800~900KB/s)** ์์ค์ ์ฐ๊ฒฐ ๋๊น ์์ด ์์ ์ ์ผ๋ก ์ ์งํ ์ ์์ต๋๋ค.