STM32F4

[STM32F4xx] Nucleo 보드 테스트 #25 (usb to uart Rx/Tx Test : KEIL)

트라이문 2018. 9. 2. 13:08
[2017.04.19] - 내용 추가 
최근에 겪었던 내용을 추가합니다.
얼마전에 여러 기능을 갖고 있는 APP에 UART Rx Interrupt 코드를 추가했는데, 천천이 데이터를 받으면 괜찮은데, 빨리 데이터를 받으면 UART RX Interrupt 기능이 동작하지 않았습니다. 
고전적인 방법을 쓰지 않고 HAL 라이브러리에 의해 프로그램을 만들어서 그런지느리고 무거운 느낌이 들었습니다.
HAL 드라이버가 다양한 기능을 한 번에 처리하고 , 사용하기 쉽다고 하지만 인터럽트 처리에 있어서는 효율이 떨어지는 것 같습니다.

위와 같이 죽었던 문제점은 HAL 드라이버를 사용하지 않았더니 없어졌습니다.
다음의 글을 참고로 코드를 작성했습니다. 설명이 아주 작 되어 있으니 참고하시기 바랍니다.


어쩌면 USB-to-UART 프로그램으로 ESP8266 에 다운로드가 안되는 문제도 위와 같이 수정하면, 잡힐 것도 같습니다.
----------------------------------------------------------------------------------------------

안녕하세요,

글을 쓸 소재들이 요즘 많아 졌는데, 올릴 시간이 없군요.

얼마 전에 PSOC5로, 
1. 일반 TR(MMBT3904)의 내부 다이오드에 정전류를 흘려서 전압을 재서 온도를 측정한 것도 있고,
2. 열전대로 온도 측정 및 열전대 측정값 디지탈 필터로 노이즈 제거,
3. PSOC5 의 새로운 콤포넌트인 LED Driver 로 7-SEGMENT 7개와 8개의 LED 다이나믹 스캔 제어(DMA로 구성되어 CPU에 부하를 주지 않음)
4. 다이오드 상온 측정값과 열전대의 목표지점의 온도를 측정하면서, PID로 전열기를 제어,
5. STM32F4xx 로 USB VCP 를 통해서 UART로 ESP-8266의 프로그램을 Upload (테스트 완료 예정)
6. ESP-8266으로 FTP 서버 만들기(SPIFF:완료,SD-CARD:테스트 중,SOFT AP FTP Server:테스트 중)의 글을 올릴 생각입니다.

이번에는 5. STM32F4xx 로 USB CDC 를 통해서 UART로 ESP-8266의 프로그램을 Upload (테스트 완료 예정) 을 
테스트하기위해 우선, STM32F4xx로 USB-to-UART 통신 테스트를 했습니다.


예전에 USB CDC 로 Tx 테스트를 해서 글을 올려 놓았는데 (http://cafe.naver.com/cortexsh/997),
USB CDC Rx(데이터를 USB Serial Port로 받는) 테스트는 정말 구현하기 힘들었습니다.

여기저기 인터넷을 뒤지고 뒤져서 겨우 단서를 하나 찾았습니다. (http://www.openstm32.org/forumthread2250)




다음의 사이트에서 STM32Cube_FW_F4_Vxx 라는 펌웨어 예제(링크)들이 있는데, 
다운 받은 zip 파일을 풀어, 
그 중에서 STM324xG EVAL/Application/USB_Device/CDC_Standalone 의 프로젝트를 참고하라고 되어 있었습니다.
이 예제를 많이 참고해서 STM32F4xx 보드로 USB-to-UART 를 구현할 수 있었습니다.

STM32F4xx 에서 사용한 내부 기능은 다음과 같습니다.
1. UART2 : UART TX (DMA 전송) , UART RX (RX Interrupt)
2. TIM2 : 5ms 마다 USB Rx로 수신된 데이터를 UART2 로 DMA 전송
3. USB Device Only : USB CDC(Communication Device Class) 초기화 함수를 실행하면,
 데이터가 자동으로(인터럽트에 의해) 수신되면서 CDC_Receive_FS() 함수가 실행됨.




[사용하지 않는 핀 설명]
위의 그림에서 
- ESP_RST(GPIO_OUT) , ESP_FLASH(GPIO_OUT) 은 나중에 ESP-8266 에 프로그램을 업로드하기 위해,
모드를 RUN-MODE,FLASH-MODE 로 변경하기 위해 설정해 놓았고 현재는 사용하지 않는다.
- 또, UART1 Rx/Tx 핀은 ESP-8266 에 프로그램을 Upload 할 때 사용하는 UART 핀으로 설정한 것으로,
현재는 사용하지 않는다.


개략적으로 프로그램 동작을 설명하자면, 
1. STM32F4xx Nucleo 보드에 UART2 와 USB CDC, TIM2 Update Interrupt 기능을 
   CUBEMX 툴로 사용하도록 설정하고 KEIL MDK 5.0 소스코드를 생성.



2. UART2 , USB CDC , TIM2 초기화.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// usb_device.c
/* init function */                        
void MX_USB_DEVICE_Init(void)
{
  /* Init Device Library,Add Supported Class and Start the library*/
  USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
 
  USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
 
  USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
 
  USBD_Start(&hUsbDeviceFS);
 
}
 
------------------------------------------------------------------------------
 
// main.c 
/* USART2 init function */
static void MX_USART2_UART_Init(void)
{
 
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
 
}
 
static void MX_DMA_Init(void
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA1_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 00);
  HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);
 
}
 
/* TIM2 init function */
static void MX_TIM2_Init(void)
{
 
  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;
 
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 960000/2;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
 
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
}
 
cs

3. UART2 RX인터럽트에 의해 데이터 수신시 USB CDC TX 큐버퍼에 데이터 저장. (in main.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  /* Increment Index for buffer writing */
  UserTxBufPtrIn++;
  
  /* To avoid buffer overflow */
  if(UserTxBufPtrIn == APP_RX_DATA_SIZE)
  {
    UserTxBufPtrIn = 0;
  }
  
  /* Start another reception: provide the buffer pointer with offset and the buffer size */
  HAL_UART_Receive_IT(huart, (uint8_t *)(UserTxBufferFS + UserTxBufPtrIn), 1);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
}
cs

4. 5ms 마다 TIM2 Update 인터럽트가 걸리면 UART2 RX로 받은 데이터가 있는지 체크해서  
USB CDC TX 큐버퍼에 있는 데이터를 USB CDC Tx 핀으로 모두 송신. (in main.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint32_t buffptr;
    uint32_t buffsize;
 /* Prevent unused argument(s) compilation warning */
    if (htim->Instance == TIM2)
    {
  
  if(UserTxBufPtrOut != UserTxBufPtrIn)
  {
    if(UserTxBufPtrOut > UserTxBufPtrIn) /* Rollback */
    {
      buffsize = APP_TX_DATA_SIZE - UserTxBufPtrOut;
    }
    else 
    {
      buffsize = UserTxBufPtrIn - UserTxBufPtrOut;
    }
    
    buffptr = UserTxBufPtrOut;
    
    USBD_CDC_SetTxBuffer(&hUsbDeviceFS, (uint8_t*)&UserTxBufferFS[buffptr], buffsize);
    
    if(USBD_CDC_TransmitPacket(&hUsbDeviceFS) == USBD_OK)
    {
      UserTxBufPtrOut += buffsize;
      if (UserTxBufPtrOut == APP_RX_DATA_SIZE)
      {
        UserTxBufPtrOut = 0;
      }
    }
  }
    }
    else if (htim->Instance == TIM1)
    {
    }
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
}
cs

5. USB CDC Rx로 인터럽트에 의해 Rx 큐버퍼에 데이터가 수신되면,
 바로 UART2 로 데이터를 DMA 방식으로 전송. (in usbd_cdc_if.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
  * @brief  CDC_Receive_FS
  *         Data received over USB OUT endpoint are sent over CDC interface 
  *         through this function.
  *           
  *         @note
  *         This function will block any OUT packet reception on USB endpoint 
  *         untill 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);
    HAL_UART_Transmit_DMA(&huart2, Buf, *Len);
  return (USBD_OK);
  /* USER CODE END 6 */ 
}
 
cs

6. 1~5번 항목을 무한 반복.


다음은 실제 Nucleo 보드와 USB 케이블 연결된 사진 입니다.





다음 그림은 프로그램을 실행함에 따라서, 장치관리자로 COM 포트가 생성된 모습입니다.




다음은 테라텀 프로그램으로 각각의 COM 포트에 연결해서 송수신 테스트를 해 본 사진입니다.




다음은 실시간 테스트 영상입니다.


마지막으로 이 프로젝트의 소스 파일을 첨부합니다.