PROJECT/USB to UART

STM32로 USB to UART 모듈 만들기 3편(FW-라인코딩)

원원 2025. 6. 14. 22:23

안녕하세요.
저번시간에는 USB to UART 할때 시리얼 통신설정을 고정으로 사용을 했었는데, 설정을 가변적으로해서 통신할수있게 해보겠습니다.

USB CDC에서 사용하는 용어인 라인코딩(Line Coding)이있는데 해당단어의 의미는 USB CDC에서 사용하는 시리얼통신설정값을 의미합니다. 라인코딩은 baudrate, data bits, stop bits, parity가 있습니다.

시리얼 통신 프로그램에서 Connect를 누르면, 라인코딩이 STM32로 전달됩니다.
실제로 Connect를 했을때 어떤값이 STM32에게 가는지 확인해보겠습니다.
시리얼 통신 프로그램은 Teraterm을 사용했고 USB to UART는 2편에서 만들었던 FW를 사용했습니다.


위의 데이터는 아래의 데이터시트에서 확인이 가능합니다.
https://www.usb.org/document-library/class-definitions-communication-devices-12

CDC120_with_Errata_and_ECN_through_Apr_3_2025.pdf, 22page


1. Class Req 33(0x21) - GET_LINE_CODING

PSTN120.pdf

Device(STM32) 의 시리얼설정을 읽어오라는 요청입니다.
그래서 STM32 -> 시리얼프로그램에 시리얼설정을 줘야하는데 1번 0x21은 처음에는 연결이력도 없고 초기데이터도 없으니 0을 주게됩니다. 3번 0x20이후에 다시 4번 0x21을 할때는 받았던 값을 주게됩니다.
사실 지금 코드에서는 4번 0x21일때 받은 시리얼설정 정보를 저장하는 코드가 들어가있지않아서 0을 줘야할거같은데, 받았던 값을 줍니다 ... 그 이유는 데이터버퍼(hcdc->data)에 있는 값이 나가는거고 동작은 정상적으로 하는것처럼 보여도 다른 사이드이펙트가 있을수있어서 명시적으로 코드를 넣어줘야합니다.


2. Class Req 32(0x20) - SET LINE CODING

PSTN120.pdf

UART설정을 Device에 적용하라는 명령입니다. 그래서 Device에게 시리얼통신설정한 정보를 주게됩니다.

PSTN120.pdf

위의 표는 시리얼통신설정 프로토콜입니다.
3번에서  "80 25 00 00 00 00 00"가 왔었습니다.
Baudrate : 80 25 00 00 (LSB , 9600bps)
Stop bits : 00
Parity : 00
Data bits : 00
Data bits가 00가 온 이유는 정확하지는 않은데 1,2번에서 받은대로 똑같이 주는거같습니다.
6번에서는 정상적으로  "80 25 00 00 00 00 08"가 왔고 Data bits가 8bits 입니다.
그리고 5번은 호스트가 디바이스에게 연결상태를 알려주는 커맨드고 이 커맨드는 다음글에서 알아보겠습니다.


결론적으로 GET_LINE_CODING일때 STM32에서 현재 갖고있는 설정값을 전달하고 SET LINE CODING일때 테라텀으로부터 받은 설정값을 저장하고 해당 설정값으로 UART를 다시 열어주면 됩니다.
이러한 설정 usbd_cdc_if.c의 CDC_Control_FS에서 하게 됩니다. 해당 코드를보면 switch case문으로 잘 구분되어 있습니다.

실제 CDC 라이브러리에서도 위에 있는 것과 동일하게 0x20, 0x21이 정의되어있습니다.

#define
CDC_SET_LINE_CODING 0x20U
#define CDC_GET_LINE_CODING 0x21U

(1) 우선 라인코딩을 저장할 변수를 선언합니다. 초기값은 아래와같이 설정했습니다.

USBD_CDC_LineCodingTypeDef LineCoding = {
  115200,                       /* baud rate */
  0x00,                         /* stop bits-1 */
  0x00,                         /* parity - none */
  0x08                          /* nb. of bits 8 */
};


(2) Class Req 33(0x21) - CDC_GET_LINE_CODING 
pbuf로 값이 들어오는데 만든 변수에 설정값을 저장해줍니다. 그리고 받은 값으로 uart포트를 다시 열기위해서 Comport_config함수를 호출합니다.

case CDC_SET_LINE_CODING: // 0x20
      LineCoding.bitrate = (uint32_t) (pbuf[0] | (pbuf[1] << 8) |
                                       (pbuf[2] << 16) | (pbuf[3] << 24));
      LineCoding.format = pbuf[4];
      LineCoding.paritytype = pbuf[5];
      LineCoding.datatype = pbuf[6];
    /* Set the new configuration */
    ComPort_Config(&huart3, &rx_data);

    break;



(3). Class Req 32(0x20) - CDC_SET_LINE_CODING 
저장되어있는 설정값을 pbuf를 통해서 호스트에게 보내주게됩니다.

    case CDC_GET_LINE_CODING: // 0x21
      pbuf[0] = (uint8_t) (LineCoding.bitrate);
      pbuf[1] = (uint8_t) (LineCoding.bitrate >> 8);
      pbuf[2] = (uint8_t) (LineCoding.bitrate >> 16);
      pbuf[3] = (uint8_t) (LineCoding.bitrate >> 24);
      pbuf[4] = LineCoding.format;
      pbuf[5] = LineCoding.paritytype;
      pbuf[6] = LineCoding.datatype;
    break;


(4) ComPort_Config함수- GET LINE CODING에서 읽은 값으로 포트를 다시 열어줍니다.
사용할 uart 핸들과 인터럽트 변수를 extern 해줍니다

extern UART_HandleTypeDef huart3;
extern uint8_t rx_data;

ComPort_Config 함수로 포트를 다시 엽니다

static void ComPort_Config(UART_HandleTypeDef *UartHandle, uint8_t *data)
{
  if (HAL_UART_DeInit(UartHandle) != HAL_OK)
  {
    Error_Handler();
  }

  switch (LineCoding.format)
  {
  case 0:
    UartHandle->Init.StopBits = UART_STOPBITS_1;
    break;
  case 2:
    UartHandle->Init.StopBits = UART_STOPBITS_2;
    break;
  default:
    UartHandle->Init.StopBits = UART_STOPBITS_1;
    break;
  }

  switch (LineCoding.paritytype)
  {
  case 0:
    UartHandle->Init.Parity = UART_PARITY_NONE;
    break;
  case 1:
    UartHandle->Init.Parity = UART_PARITY_ODD;
    break;
  case 2:
    UartHandle->Init.Parity = UART_PARITY_EVEN;
    break;
  default:
    UartHandle->Init.Parity = UART_PARITY_NONE;
    break;
  }

  switch (LineCoding.datatype)
  {
  case 0x07:
    UartHandle->Init.WordLength = UART_WORDLENGTH_8B;
    break;
  case 0x08:
    if (UartHandle->Init.Parity == UART_PARITY_NONE)
      UartHandle->Init.WordLength = UART_WORDLENGTH_8B;
    else
      UartHandle->Init.WordLength = UART_WORDLENGTH_9B;
    break;
  default:
    UartHandle->Init.WordLength = UART_WORDLENGTH_8B;
    break;
  }

  UartHandle->Init.BaudRate = LineCoding.bitrate;
  UartHandle->Init.HwFlowCtl = UART_HWCONTROL_NONE;
  UartHandle->Init.Mode = UART_MODE_TX_RX;
  UartHandle->Init.OverSampling = UART_OVERSAMPLING_16;

  if (HAL_UART_Init(UartHandle) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_UART_Receive_IT(UartHandle, data, 1);
}

 

이제 실제 테스트로 확인해보겠습니다.

1. 시리얼 터미널로 테스트
baudrate를 바꿔도 동작을 합니다.

2. USB 패킷 테스트

터미널프로그램에서 9600으로 연결했습니다.
초기 연결시 stm32에는 bps가 115200(0x01c200)으로 설정되어있었으므로 115200으로 응답했고 그 이후에는 터미널프로그램에서 9600(0x2580)으로 설정했고 stm32에서도 9600으로 응답했습니다.

터미널프로그램에서 38400 bps, 7bit, odd, 2bit로 설정했습니다.
00 96 00 00 02 01 07
baudrate : 0x9600 (38400)
stopbits : 2
parity : 1 (odd)
data : 7

소스코드(Commits: Line coding update)
https://github.com/yhunterr/STM32_USBtoUART


다음글에서는  Class Req 34커맨드에 대해 알아보겠습니다.