10 분 소요

3. USB VCP

USB 쪽 코드를 손대기 전에, 개인적으로 자료형 선언을 간단하게 하기 위한 헤더파일 하나를 추가하자.

/*******************************************************************************
* File Name          : stm32f4xx_type.h
* Author             : kyj
* Version            : V1.0.0
* Date               : 01/05/2020
* Description        : This file contains all the common data types used for the
*                      STM32F4xx firmware library.
********************************************************************************
* THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
* WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE TIME.
* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE
* CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*******************************************************************************/

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F4xx_TYPE_H
#define __STM32F4xx_TYPE_H

/* Includes ------------------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/
typedef int64_t	s64;
typedef int32_t	s32;
typedef int16_t	s16;
typedef int8_t	s8;

typedef int64_t const	sc64;  /* Read Only */
typedef int32_t const	sc32;  /* Read Only */
typedef int16_t const	sc16;  /* Read Only */
typedef int8_t const	sc8;   /* Read Only */

typedef volatile int64_t	vs64;
typedef volatile int32_t	vs32;
typedef volatile int16_t	vs16;
typedef volatile int8_t		vs8;

typedef volatile int64_t const	vsc64;  /* Read Only */
typedef volatile int32_t const	vsc32;  /* Read Only */
typedef volatile int16_t const	vsc16;  /* Read Only */
typedef volatile int8_t const	vsc8;   /* Read Only */

typedef uint64_t	u64;
typedef uint32_t	u32;
typedef uint16_t	u16;
typedef uint8_t		u8;

typedef uint64_t const	uc64;  /* Read Only */
typedef uint32_t const	uc32;  /* Read Only */
typedef uint16_t const	uc16;  /* Read Only */
typedef uint8_t const	uc8;   /* Read Only */

typedef volatile uint64_t	vu64;
typedef volatile uint32_t	vu32;
typedef volatile uint16_t	vu16;
typedef volatile uint8_t	vu8;

typedef volatile uint64_t const	vuc64;  /* Read Only */
typedef volatile uint32_t const	vuc32;  /* Read Only */
typedef volatile uint16_t const	vuc16;  /* Read Only */
typedef volatile uint8_t const	vuc8;   /* Read Only */

typedef float	f32;
typedef double	f64;

typedef float const		fc32;  /* Read Only */
typedef double const	fc64;  /* Read Only */

typedef volatile float	vf32;
typedef volatile double	vf64;

typedef volatile float const	vfc32;  /* Read Only */
typedef volatile double const	vfc64;  /* Read Only */

#define U8_MAX     ((u8)255)
#define S8_MAX     ((s8)127)
#define S8_MIN     ((s8)-128)
#define U16_MAX    ((u16)65535U)
#define S16_MAX    ((s16)32767)
#define S16_MIN    ((s16)-32768)
#define U32_MAX    ((u32)4294967295UL)
#define S32_MAX    ((s32)2147483647)
#define S32_MIN    ((s32)-2147483648)
#define U64_MAX    ((u64)18446744073709551615ULL)
#define S64_MAX    ((s32)9223372036854775807)
#define S64_MIN    ((s32)-9223372036854775808)

/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */

#endif /* __STM32F4xx_TYPE_H */

/*********************************END OF FILE**********************************/

위 코드를 “stm32f4xx_type.h” 이름으로 프로젝트의 “Core/Inc” 디렉토리 안에 저장하자.
그리고, main.h” 파일에서 /* Private includes ———————————————————-*/ 아래에 User Code로 include 해 주자.
image
이제부터는 변수를 선언할 때, u8, u16, s32 등과 같이 간단하게 선언할 수 있다.

(1) VCP 인터페이스 함수 작성

다음 내용의 vcp.c, vcp.h 파일을 만들어주자.
코드는 각각 [프로젝트 폴더]/Core/Src
       [프로젝트 폴더]/Core/Inc
위치에 저장해 주자.

vcp.h

#ifndef __VCP_H__
#define __VCP_H__

#include "main.h"

#define RING_BUFF_SIZE			(2048)

// Ring buffer
typedef struct _user_que {
	u8 buff[RING_BUFF_SIZE];
	u32 pop;
	u32 push;
} user_que;

extern void que_clear(user_que *que);
extern u8 que_is_empty(user_que *que);
extern u8 que_is_full(user_que *que);
extern u32 que_recv_size(user_que *que);
extern u32 que_push(user_que *que, u8 data);
extern u8 que_pop(user_que *que, u8 *data);

extern user_que g_stRecv_buff;
// Ring buffer

extern vu8 g_vucVCP_TxComplete;

extern void	VCP_Init(void);
extern void	VCP_Deinit(void);
extern u32	uiVCP_GetBaudrate(void);

extern u32	uiVCP_GetRecvSize(void);
extern void	VCP_RxBuffClear(void);

extern u8	ucVCP_ReadByte(u8 *pucByte);
extern u32	uiVCP_ReadBuff(u8 *pucBuff, u32 uiSize);

extern u8	ucVCP_WriteByte(u8 byte);
extern u32	uiVCP_WriteBuff(u8 *pucBuff, u32 uiSize);
extern void	VCP_Printf(const char *format, ...);

#endif /*__VCP_H__*/

vcp.c

#include <stdio.h>
#include <stdarg.h>				// va_start(),va_end()

#include "vcp.h"
#include "gpio.h"
#include "usb_device.h"
#include "usbd_cdc.h"
#include "usbd_cdc_if.h"

extern uint8_t UserRxBufferFS[];
extern USBD_HandleTypeDef hUsbDeviceFS;
extern USBD_CDC_LineCodingTypeDef LineCoding;

#define USB_CLASSDATA		(*(USBD_CDC_HandleTypeDef *)hUsbDeviceFS.pClassData)

user_que g_stRecv_buff;
vu8 g_vucVCP_TxComplete = 1;

void VCP_Init(void)
{
	que_clear(&g_stRecv_buff);
}

void VCP_Deinit(void)
{
	USBD_LL_DeInit(&hUsbDeviceFS);
}

u32 uiVCP_GetBaudrate(void)
{
	return LineCoding.bitrate;
}

/****************************** Ring buff ***************************************/
void que_clear(user_que *que)
{
	memset(que, 0, sizeof(user_que));
}

u8 que_is_empty(user_que *que)
{
	if(que->pop == que->push) {
		return 1;
	}
	else return 0;
}

u8 que_is_full(user_que *que)
{
	if(que->pop == (que->push + 1)%RING_BUFF_SIZE) {
		return 1;
	}
	else {
		return 0;
	}
}

u32 que_recv_size(user_que *que)
{
	if(que->pop <= que->push) {
		return (que->push - que->pop);
	}
	else {
		return (RING_BUFF_SIZE - (que->pop - que->push));
	}
}

u32 que_push(user_que *que, u8 data)
{
	if(que_is_full(que)) {
		return 0;
	}

	que->buff[que->push] = data;
	que->push = (que->push + 1)%RING_BUFF_SIZE;

	return 1;
}

u8 que_pop(user_que *que, u8 *data)
{
	if(que_is_empty(que)) {
		return 0;
	}

	*data = que->buff[que->pop];
	que->pop = (que->pop + 1)%RING_BUFF_SIZE;

	return 1;
}
/****************************** Ring buff ***************************************/

u32 uiVCP_GetRecvSize(void)
{
	u32 len;

	len = que_recv_size(&g_stRecv_buff);

	return len;
}

void VCP_RxBuffClear(void) {
	que_clear(&g_stRecv_buff);
}

u8 ucVCP_ReadByte(u8 *pucByte)
{
	if(0 == que_recv_size(&g_stRecv_buff)) {
		return 0;
	}
	que_pop(&g_stRecv_buff, pucByte);

	return 1;
}

u32	uiVCP_ReadBuff(u8 *pucBuff, u32 uiSize)
{
	u32 cnt;

	for(cnt = 0; cnt < uiSize; cnt++) {
		if(ucVCP_ReadByte(&pucBuff[cnt]) == 0) {
			break;
		}
	}

	return cnt;
}

u8 ucVCP_WriteByte(u8 ucByte)
{
	USBD_CDC_SetTxBuffer(&hUsbDeviceFS, &ucByte, 1);
	if(USBD_OK == USBD_CDC_TransmitPacket(&hUsbDeviceFS)) return 1;
	else return 0;
}

u32 uiVCP_WriteBuff(u8 *pucBuff, u32 uiSize)
{
  u32 len = 0;
	u32 timeout;

	if((USBD_STATE_CONFIGURED != hUsbDeviceFS.dev_state) ||
	   (0 != USB_CLASSDATA.TxState) ||
	   (0 == uiSize) ||
	   (APP_TX_DATA_SIZE < uiSize))
	{
		return 0;
	}

	g_vucVCP_TxComplete = 0;
	USBD_CDC_SetTxBuffer(&hUsbDeviceFS, pucBuff, uiSize);
	timeout = 100000;
	while(USBD_OK != USBD_CDC_TransmitPacket(&hUsbDeviceFS)) {
		timeout--;
		if(0 == timeout) {
			return 0;
		}
	}
	timeout = 100000;
	while(0 == g_vucVCP_TxComplete) {
		timeout--;
		if(0 == timeout) {
			return 0;
		}
	}
	len = USB_CLASSDATA.TxLength;

	return len;
}

void VCP_Printf(const char *format, ...)
{
	char str[256];
	va_list va;

	va_start(va, format);
	vsprintf(str, format, va);
	va_end(va);

	uiVCP_WriteBuff((u8 *)str, strlen(str));
}

(2) usbd_cdc_if.c 파일 수정

/* USER CODE BEGIN INCLUDE */ 부분에 vcp.h 헤더파일을 include 해 주자.

/* Includes ------------------------------------------------------------------*/
#include "usbd_cdc_if.h"

/* USER CODE BEGIN INCLUDE */
#include "vcp.h"
/* USER CODE END INCLUDE */

/* USER CODE BEGIN PRIVATE_TYPES */ 부분에 LineCoding 타입 구조 변수 선언 및 초기화 (코드 97번째 줄)

/** @defgroup USBD_CDC_IF_Private_Variables USBD_CDC_IF_Private_Variables
  * @brief Private variables.
  * @{
  */
/* Create buffer for reception and transmission           */
/* It's up to user to redefine and/or remove those define */
/** Received data over USB are stored in this buffer      */
uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];

/** Data to send over USB CDC are stored in this buffer   */
uint8_t UserTxBufferFS[APP_TX_DATA_SIZE];

/* USER CODE BEGIN PRIVATE_VARIABLES */
USBD_CDC_LineCodingTypeDef LineCoding = {
		115200, // baud rate
		0x00,   // stop bits: 1
		0x00,   // parity: none
		0x08    // number of bits: 8
};
/* USER CODE END PRIVATE_VARIABLES */

CDC_SET_LINE_CODING case와 CDC_GET_LINE_CODING case에서 다음과 같이 내용을 추가해 준다.

/**
  * @brief  Manage the CDC class requests
  * @param  cmd: Command code
  * @param  pbuf: Buffer containing command data (request parameters)
  * @param  length: Number of data to be sent (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length)
{
  /* USER CODE BEGIN 5 */
  switch(cmd)
  {
    case CDC_SEND_ENCAPSULATED_COMMAND:

    break;

    case CDC_GET_ENCAPSULATED_RESPONSE:

    break;

    case CDC_SET_COMM_FEATURE:

    break;

    case CDC_GET_COMM_FEATURE:

    break;

    case CDC_CLEAR_COMM_FEATURE:

    break;

  /*******************************************************************************/
  /* Line Coding Structure                                                       */
  /*-----------------------------------------------------------------------------*/
  /* Offset | Field       | Size | Value  | Description                          */
  /* 0      | dwDTERate   |   4  | Number |Data terminal rate, in bits per second*/
  /* 4      | bCharFormat |   1  | Number | Stop bits                            */
  /*                                        0 - 1 Stop bit                       */
  /*                                        1 - 1.5 Stop bits                    */
  /*                                        2 - 2 Stop bits                      */
  /* 5      | bParityType |  1   | Number | Parity                               */
  /*                                        0 - None                             */
  /*                                        1 - Odd                              */
  /*                                        2 - Even                             */
  /*                                        3 - Mark                             */
  /*                                        4 - Space                            */
  /* 6      | bDataBits  |   1   | Number Data bits (5, 6, 7, 8 or 16).          */
  /*******************************************************************************/
    case CDC_SET_LINE_CODING:
      LineCoding.bitrate    = *(uint32_t *)pbuf;
      LineCoding.format     = pbuf[4];
      LineCoding.paritytype = pbuf[5];
      LineCoding.datatype   = pbuf[6];
    break;

    case CDC_GET_LINE_CODING:
      *(uint32_t *)pbuf = LineCoding.bitrate;
      pbuf[4] = LineCoding.format;
      pbuf[5] = LineCoding.paritytype;
      pbuf[6] = LineCoding.datatype;
    break;

    case CDC_SET_CONTROL_LINE_STATE:

    break;

    case CDC_SEND_BREAK:

    break;

  default:
    break;
  }

  return (USBD_OK);
  /* USER CODE END 5 */
}

그리고, CDC_Receive_FS 함수 내용을 다음과 같이 수정하자.

/**
  * @brief  Data received over USB OUT endpoint are sent over CDC interface
  *         through this function.
  *
  *         @note
  *         This function will issue a NAK packet on any OUT packet received on
  *         USB endpoint until exiting this function. If you exit this function
  *         before transfer is complete on CDC interface (ie. using DMA controller)
  *         it will result in receiving more data while previous ones are still
  *         not sent.
  *
  * @param  Buf: Buffer of data to be received
  * @param  Len: Number of data received (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  for(u32 i = 0; i < *Len; i++) {
  	que_push(&g_stRecv_buff, Buf[i]);
  }
  return (USBD_OK);
  /* USER CODE END 6 */
}

마지막으로, CDC_TransmitCplt_FS 함수에서 전송 완료를 알리는 flag 변수를 Set해주도록 하자.

/**
  * @brief  CDC_TransmitCplt_FS
  *         Data transmitted callback
  *
  *         @note
  *         This function is IN transfer complete callback used to inform user that
  *         the submitted Data is successfully sent over USB.
  *
  * @param  Buf: Buffer of data to be received
  * @param  Len: Number of data received (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_TransmitCplt_FS(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
{
  uint8_t result = USBD_OK;
  /* USER CODE BEGIN 13 */
  UNUSED(Buf);
  UNUSED(Len);
  UNUSED(epnum);
  g_vucVCP_TxComplete = 1;
  /* USER CODE END 13 */
  return result;
}

(2) VCP 인터페이 함수 활용

이제 main.c 코드에 다음과 같이 vcp.h 를 include 해 주고, VCP_Init() 함수를 불러서 초기화 해 주고, while loop 에서 VCP로 받은 데이터를 그대로 출력해 주는 프로그램을 작성해 보자.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usb_device.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "vcp.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */
  VCP_Init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  u8 pucBuff[64];
  u32 uiSize;
  while (1)
  {
    uiSize = uiVCP_ReadBuff(pucBuff, 64);
    if(0 < uiSize) {
      uiVCP_WriteBuff(pucBuff, uiSize);
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

코드를 컴파일 하고, 보드에 다운로드 한 뒤에, USB-C 단자를 PC와 연결하면 COM Port가 생성되는 것을 확인할 수 있다.
image
Putty 터미널을 이용하여 생성된 COM Port 를 열고 통신을 해 보면 정상적으로 작동하는 것을 볼 수 있다.
image
image
참고로, Port 설정에서 9600에 해당하는 Speed는 실질적인 통신 속도와는 아무런 관계가 없다.
코드에서 추가한 Linecoding 구조체 값에 반영이 되지만, 실제 신호의 속도가 바뀌는 것은 아니다.

카테고리:

업데이트:

댓글남기기