STM32F4 개발보드 프로그래밍 ③ (USB VCP)
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 해 주자.
이제부터는 변수를 선언할 때, 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>© 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가 생성되는 것을 확인할 수 있다.
Putty 터미널을 이용하여 생성된 COM Port 를 열고 통신을 해 보면 정상적으로 작동하는 것을 볼 수 있다.
참고로, Port 설정에서 9600에 해당하는 Speed는 실질적인 통신 속도와는 아무런 관계가 없다.
코드에서 추가한 Linecoding 구조체 값에 반영이 되지만, 실제 신호의 속도가 바뀌는 것은 아니다.
댓글남기기