[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 포트에 연결해서 송수신 테스트를 해 본 사진입니다.




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


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


이번에는 STM32F446에서 USB CDC(Communication Device Class) = USB Virtual Comport Serial Driver 기능을 테스트해 보겠습니다. 


회사에서 제작한 보드에는 WLCSP 타입의 STM32F446 IC를 사용해서 CUBEMX 툴에서 패키지를 바꿨습니다.

[회사에서 제작한 보드]



아이고 그런데, 설계를 USB HS 포트에 연결을 해놨네.. @@
이번에 처음 테스트를 했는데, 결과적으로 USB DFU 와 CDC 모두 잘 됐습니다.

USB CDC 는 정말 간단합니다. CUBEMX 툴에서 설정만 제대로 하면 거의 다 된 것입니다.
하지만 아무리 해도 CDC가 UART 포트로 잘 안잡혔는데, 유튜브 동영상 보고 수정하니까 잘 됩니다.
이 내용은 나중에 말씀드리겠습니다.

먼저 CUBEMX 툴로 PINOUT 설정에서 필요한 기능들을 정의합니다.

1. USB_HS 포트에 internal FS Phy 를 Device Only 로 설정.



2. RCC 에서 HSE 를 Crystal 로 설정. MiddleWares 의 USB_DEVICE 에 CDC로 설정.



3. 클럭 설정에서 외부 클락을 24MHz로 수정하고 HCLK 를 180MHz로 수정하고 엔터치면 오랜 시간이 걸린 후에 168MHz로 자동으로 계산해 주는데, 자주 하다 보니 그냥 처음부터 168MHz를 써 주면 금방 설정이 완료 됩니다.



4. 마무리로 프로젝트 이름 넣어주고 Toolchain / IDE를 Keil로 설정하고 Generate code 를 클릭하면 Keil 코드가 만들어 집니다.



5. 그냥 이대로 KEIL 에서 컴파일해서 다운로드해 주면 다음과 같이 PC 장치관리자에서 USB CDC 는 보이는데,
오류가 발생합니다.




다음의 Youtube 영상의 2:36 부분부터 유심히 보시면 저와 같은 결과에 어떻게 대응하는지 잘 나와 있습니다.
저도 이 영상보고 수정해서 성공했습니다.






6. 수정할 코드는 usbd_cdc.h 의 CDC_DATA_HS_MAX_PACKET_SIZE 의 값이 잘 못 되어 있습니다. (^^ 저는 원리도 모르겠고 그냥 따라했는데 잘 되요.)
아뭏든 512 를 256 으로 고치면 정상적으로 PC에서 COM PORT가 잡힙니다.



7. PC장치관리자에서 포트 확인




8. 이렇게까지 하면 포트만 생성됐지 터미날 연결하면 아무짓도 안하는게 당연하겠죠? ^^
USB UART 출력 함수는 usbd_cdc_if.c 파일 안에 uint8_t CDC_Transmit_HS(uint8_t* Buf, uint16_t Len) 함수를 사용하면 됩니다.

간단한 예로,
CDC_Transmit_HS("Try..!!\n\r",9); 하면 
터미날에 ..
Try..!!

라고 출력 됩니다.

그런데 printf 함수가 참 편하므로 이것도 printf() 함수 쓰는 방법을 알아보겠습니다.
예전에 UART에서 사용하던 것과 같이 tx 함수만 CDC_Transmit_HS() 함수로 수정해 주면 됩니다.

main.c 파일에 다음과 같이 추가해 주시면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
/* USER CODE BEGIN 0 */
 
#ifdef __GNUC__
 #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
 #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
 
PUTCHAR_PROTOTYPE
{
 while(CDC_Transmit_HS((uint8_t *)&ch, 1== USBD_BUSY);
 return ch;
}
cs



그런 다음에 필요한 곳에서 printf() 함수를 사용하시면 됩니다.
끝...

이런 한가지 빼 먹었습니다. #include 에 다음과 같이 추가해 주세요.
1
2
3
4
5
6
7
8
9
10
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_hal.h"
#include "usb_device.h"
 
/* USER CODE BEGIN Includes */
#include "string.h"
#include "usbd_cdc_if.h"
/* USER CODE END Includes */
cs

이것은 따로 프로젝트 파일 첨부하지 않겠습니다.
너무 간단하니까요. ^^


일반적인 JTAG으로 다운로드하는 프로그램과 다 똑같고,

수정할 부분은 다음의 3가지 입니다.

1. configuration 의 target TAB의 IROM 영역 설정.



2. DFUse 프로그램에서 DFU 전용 업그레이드 프로그램으로 변환하기 위해서 HEX 파일 출력 설정.



3. system_stm32f4xx.c 파일에서 VECTER Table Offset 값 수정.



이렇게 설정하고 컴파일을 하면 HEX 파일이 나옵니다.
이 파일을 DFU 파일로 변환합니다.

다음은 변환툴(HEX ->DFU) 입니다. STM에서 DFUSe 툴을 검색해서 인스톨하면 같이 깔리는 DFU File Manager 라는 툴이 있습니다.
1. 이 프로그램을 실행해서 HEX->DFU 옵션을 선택하고 OK.



2. Vendor ID를 0x0483 ,Product ID 를 0xDF11 로 설정하고 Version 은 적당하게 설정합니다.
3. S19 or HEX 버튼을 클릭해서 위에서 만들어진 APP 프로그램의 결과물인 HEX 파일을 선택한다.



4. Generate 버튼을 눌러서 DFU 파일을 생성한다.



지금까지 DFU Upgrade용 파일을 생성하는 과정이었고, 다음은 이 DFU 파일을 Upload 하는 과정입니다.
1. DFUSe Demo 프로그램을 실행한다.



2. DFU Bootloader 가 실행될 조건으로 보드를 설정하여 컴퓨터의 USB 포트에 연결하면 다음과 같은 내부 정보가 DFUSe 프로그램에 표시된다.



3. Upgrade or Verify Action 구간에 있는 Choose 버튼을 클릭해서 DFU Manager 에서 만든 DFU 파일을 읽어 온다.



4. Upgrade 버튼을 클릭하여 프로그램을 Board에 Upgrade 한다.



5. Upgrade 가 완료 되면 다음과 같이 Upgrade Successful! 메세지가 뜬다.



6. 리셋 버튼을 눌러주면 부트로더 프로그램에서 APP 가 정상인지를 체크해서 APP 프로그램으로 점프해서 APP 프로그램이 실행된다.




안녕하세요, 얼른 올려야 했는데 너무 늦었네요.


지난 번에 CUBEMX 툴로 부트로더 만들어서 실행하면 윈도우 장치 드라이버에 DFU 드라이버가 생성되는 것 까지 했었죠.

여기까지 1차로 진행이 잘 된 것이고,

두번째로 추가할 코드들이 있습니다.

두개의 파일에 손을 대야 합니다. 
main.c  과 usbd_dfu_if.c 입니다.

먼저 main.c 에서 고쳐야 할 내용은 다음과 같습니다.
1. APP 프로그램(부트로더 프로그램이 아닌) 이 현재 USBD_DFU_APP_DEFAULT_ADD 위치에 들어 있는지 검사하는 내용.
2. APP 가 존재하면 APP 프로그램으로 점프.
3. 강제로 부트로더 진입조건을 설정하여 DFU Upgrade 모드로 들어갈 수 있다.(User Switch를 누름으로써)
3. 존재하지 않으면 DFU Upgrade 프로그램 실행.
4. MX_USB_DEVICE_Init() 함수는 Cube MX 툴이 Source 코드를 생성할 때마다 자동으로 다시 위치를 앞쪽에 잡아 놓는데, APP 프로그램이 메모리에 없을때만 동작하도록 뒷쪽으로 위치 이동 시켜야 합니다.

해당 코드는 다음과 같습니다.
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
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();
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  //MX_USB_DEVICE_Init();
 
  /* USER CODE BEGIN 2 */
    HAL_Delay(500);
    printf("DFU Program Start.. \n\r");
 
    // Test if User button on the Necleo kit is pressed 
  if (HAL_GPIO_ReadPin(USER_SW_IN_GPIO_Port,USER_SW_IN_Pin) != GPIO_PIN_RESET)
  {
            printf("USBD_DFU_APP_DEFAULT_ADD ..[%08X]\n\r",(*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD));
    // Check Vector Table: Test if user code is programmed starting from address 
    //   "APPLICATION_ADDRESS" 
    if (((*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000 ) == 0x20000000)
    {
            printf("APP Start.. \n\r");
      // Jump to user application 
      JumpAddress = *(__IO uint32_t*) (USBD_DFU_APP_DEFAULT_ADD + 4);
      Jump_To_Application = (pFunction) JumpAddress;
      // Initialize user application's Stack Pointer 
      __set_MSP(*(__IO uint32_t*) USBD_DFU_APP_DEFAULT_ADD);
      Jump_To_Application();
    }
  }
    
    printf("DFU Upgrade Mode Start.. \n\r");
  MX_USB_DEVICE_Init();
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
 
  }
  /* USER CODE END 3 */
 
}
cs

여기서 설명이 좀 필요한 부분이 APP 시작 주소를 검사하여 프로그램이 메모리에 들어있는지를 검사하는 코드인데,
해당 코드는 다음과 같습니다.
1
2
3
4
    if (((*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000 ) == 0x20000000)
    {
 
    }
cs

APP 시작 주소(USBD_DFU_APP_DEFAULT_ADD)는 나중에 부트로더에 의해 Upload될 APP프로그램이 위치할 시작 주소입니다.
이 위치에는 Stack 포인터가 위치하는데, 이 Stack Pointer 는 램 영역 내에 주소값을 갖어야 정상적인 프로그램으로 인식합니다.
128KByte 의 램영역은 0x2000 0000 ~ 0x2001 FFFF 입니다.

위의 계산식을 풀어서 쓰면 
1
2
3
4
if ( (*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) >= 0x20000000  && \
     (*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) <  0x20020000       )
{
}
cs
와 같습니다.
풀어서 보면 쉽게 아시겠죠? 저도 처음에 왜 이렇게 쓰는지 골머리를 알았습니다. ^^

그다음에 __set_MSP() 함수로 스택포인터를 초기화한 후, user Application 프로그램으로 점프하면 Upload된 user 프로그램이 실행됩니다.


이번에는 usbd_dfu_if.c 의 내용을 수정해야 하는데, 추가할 내용이 꽤 많네요.
내용의 설명은 하지 않고 수정할 코드만 표시하겠습니다. 내용은 내부 Flash 메모리 읽기/쓰기/지우기 내용들입니다.
내부 Flash 메모리 읽기/쓰기/지우기 내용은 나중에 따로 다룰 예정입니다.

다음과 같은 함수를 추가합니다.
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
/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
static uint32_t GetSectorSize(uint32_t Sector);
static uint32_t GetSector(uint32_t Address);
/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */
 
/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */
static uint32_t GetSector(uint32_t Address)
{
  uint32_t sector = 0;
  
  if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0))
  {
    sector = FLASH_SECTOR_0;  
  }
  else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1))
  {
    sector = FLASH_SECTOR_1;  
  }
  else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2))
  {
    sector = FLASH_SECTOR_2;  
  }
  else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3))
  {
    sector = FLASH_SECTOR_3;  
  }
  else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4))
  {
    sector = FLASH_SECTOR_4;  
  }
  else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5))
  {
    sector = FLASH_SECTOR_5;  
  }
  else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6))
  {
    sector = FLASH_SECTOR_6;  
  }
  else/*(Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_7))*/
  {
    sector = FLASH_SECTOR_7;  
  }
 
  return sector;
}
 
/**
  * @brief  Gets sector Size
  * @param  None
  * @retval The size of a given sector
  */
static uint32_t GetSectorSize(uint32_t Sector)
{
  uint32_t sectorsize = 0x00;
  if((Sector == FLASH_SECTOR_0) || (Sector == FLASH_SECTOR_1) || (Sector == FLASH_SECTOR_2) ||\
     (Sector == FLASH_SECTOR_3) )
  {
    sectorsize = 16 * 1024;
  }
  else if(Sector == FLASH_SECTOR_4)
  {
    sectorsize = 64 * 1024;
  }
  else
  {
    sectorsize = 128 * 1024;
  }  
  return sectorsize;
}
 
cs

내용이 빈 함수들에 다음과 같이 추가합니다.

uint16_t MEM_If_Init_FS(void) 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
uint16_t MEM_If_Init_FS(void)
  /* USER CODE BEGIN 0 */ 
  return (USBD_OK);
  /* USER CODE END 0 */ 
}
 
// 다음과 같이 수정
uint16_t MEM_If_Init_FS(void)
  /* USER CODE BEGIN 0 */ 
    HAL_FLASH_Unlock();  
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |  
                           FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);  
  return (USBD_OK);
  /* USER CODE END 0 */ 
}
cs

uint16_t MEM_If_DeInit_FS(void) 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uint16_t MEM_If_DeInit_FS(void)
  /* USER CODE BEGIN 1 */ 
  return (USBD_OK);
  /* USER CODE END 1 */ 
}
 
// 다음과 같이 수정
uint16_t MEM_If_DeInit_FS(void)
  /* USER CODE BEGIN 1 */ 
    HAL_FLASH_Lock();  
  return (USBD_OK);
  /* USER CODE END 1 */ 
}
cs


uint16_t MEM_If_Erase_FS(uint32_t Add) 함수
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
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
  /* USER CODE BEGIN 2 */ 
  return (USBD_OK);
  /* USER CODE END 2 */ 
}
 
// 다음과 같이 수정
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
  /* USER CODE BEGIN 2 */ 
    uint32_t UserStartSector;  
    uint32_t SectorError;  
    FLASH_EraseInitTypeDef pEraseInit;  
    MEM_If_Init_FS();  
    
    UserStartSector = GetSector(Add);  
    
    pEraseInit.TypeErase = TYPEERASE_SECTORS;  
    pEraseInit.Sector = UserStartSector;  
    pEraseInit.NbSectors = 3;  
    pEraseInit.VoltageRange = VOLTAGE_RANGE_3;  
    if(HAL_FLASHEx_Erase(&pEraseInit,&SectorError)!=HAL_OK)  
    {  
            return (USBD_FAIL);  
    }     
  return (USBD_OK);
  /* USER CODE END 2 */ 
}
cs

uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len) 함수
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
uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* USER CODE BEGIN 3 */ 
  return (USBD_OK);
  /* USER CODE END 3 */ 
}
 
// 다음과 같이 수정
uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* USER CODE BEGIN 3 */ 
    uint32_t i = 0;   
    for(i = 0; i < Len; i = i + 4)  
    {  
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,(uint32_t)(dest + i),*(uint32_t *)(src + i)) == HAL_OK)  
            {  
                    if(*(uint32_t *)(src + i) != *(uint32_t *)(dest + i))  
                    {  
                            return 2;  
                    }  
            }  
            else  
            {  
                    return 1;  
            }  
    }     
  return (USBD_OK);
  /* USER CODE END 3 */ 
}
cs

uint8_t *MEM_If_Read_FS (uint8_t *src, uint8_t *dest, uint32_t Len)함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
uint8_t *MEM_If_Read_FS (uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* Return a valid address to avoid HardFault */
  /* USER CODE BEGIN 4 */ 
  return (uint8_t*)(USBD_OK);
  /* USER CODE END 4 */ 
}
 
// 다음과 같이 수정
uint8_t *MEM_If_Read_FS (uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* Return a valid address to avoid HardFault */
  /* USER CODE BEGIN 4 */ 
    uint32_t i = 0;  
    uint8_t *psrc = src;  
    for( i = 0; i < Len ; i++ )  
    {  
            dest[i] = *psrc++;  
    }  
  return (uint8_t*)(USBD_OK);
  /* USER CODE END 4 */ 
}
cs


uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer) 함수
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
uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
  /* USER CODE BEGIN 5 */ 
  switch (Cmd)
  {
  case DFU_MEDIA_PROGRAM:
 
    break;
    
  case DFU_MEDIA_ERASE:
  default:
 
    break;
  }                             
  return  (USBD_OK);
  /* USER CODE END 5 */  
}
 
// 다음과 같이 수정
uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
  /* USER CODE BEGIN 5 */ 
    uint16_t FLASH_PROGRAM_TIME = 50;  
    uint16_t FLASH_ERASE_TIME = 50;  
  switch (Cmd)
  {
  case DFU_MEDIA_PROGRAM:
        buffer[1= (uint8_t)FLASH_PROGRAM_TIME;  
        buffer[2= (uint8_t)(FLASH_PROGRAM_TIME << 8);  
        buffer[3= 0;  
    break;
    
  case DFU_MEDIA_ERASE:
  default:
        buffer[1= (uint8_t)FLASH_ERASE_TIME;  
        buffer[2= (uint8_t)(FLASH_ERASE_TIME << 8);  
        buffer[3= 0;  
    break;
  }                             
  return  (USBD_OK);
  /* USER CODE END 5 */  
}
cs


설명할 내용이 많아서 다음과 같이 3번에 나눠서 글을 써야겠네요.

1/3 : DFU-Bootloader 툴 설정
2/3 : DFU-Bootloader 프로그램 수정
3/3 : DFU-Bootloaderble APP 프로그램 수정

오늘은 1/3 : DFU-Bootloader 툴 설정 에 관해서 글을 써 보겠습니다.

STM32F4xx 중에서 제가 테스트한 MCU는 STM32F411 과 STM32F446 입니다.
두 디바이스 모두, DFU(Device Firmware Upgrade) 동작이 잘 됩니다.

DFU는 2가지 방법으로 구현을 할 수 있는데,
1. Embeded DFU mode : MCU 내부에 이미 들어있는 기능으로 평소에 JTAG로 동작하던 일반적인 펌웨어를 특정 Flash Memory 주소(0x08000000)에 다운로드할 수 있고,다음과 같은 특정한 핀 설정으로 진입한다. 초기 부팅 시 Boot1 핀은 PB2 핀과 기능을 공유한다.



2. Custom DFU Mode : MCU Flash 메모리에 User가 필요에 따라서 각종 DFU 진입 조건(사용자가 일반 핀입력이나 여러가지 조건으로 지정할 수 있다)과 APP Flash Memory 시작 주소를 수정할 수 있다. APP 프로그램의 설정을 수정해야 하는 번거러움이 있다.


제가 Embeded DFU를 사용하지 않고 Custom DFU를 사용하려 하는 이유는,
APP f/w 가 Flash MEM 의 최초 시작 주소부터 프로그램의 기능에 따라 늘어나는데, 얼마나 크게 만들어 질지 몰라서 
1. Flash data Memory(EEPROM Emulation 기능)를 위치시킬 주소를 정하기가 번거롭고,

여유롭게 뒤쪽에 있는 Sector에 data Flash를 위치 시키려고 보니,
뒤쪽으로 갈수록 Sector 당 Flash MEM 크기가 너무 큰 단위로 증가를 해서 버리는 메모리가 많았습니다.
Flash 메모리를 쓸려면 일단 Erase 해야 하는데, Erase 방법이 Setor 별로 지우던가 전체 메모리를 지워던가 2가지밖에 없었습니다.
그래서 또 다음과 같은 불합리한 문제가 있어서 Custom DFU를 사용하게 되었습니다.
2. 뒤로 갈수로 Sector 당 Flash MEM 크기가 증가하여 낭비되는 메모리가 많다.
3. Sector 당 메모리가 크면 1 sector를 지우는데 시간이 많이 걸린다.

STM32F411 과 STM32F446 은 플래쉬 메모리의 크기에 따라 2가지(256KB,512KB)로 나뉩니다.
그에 따른 Sector 구조는 다음과 같습니다.







Necleo 보드는 USB 콘넥터가 보드내에 없기 때문에 DFU 테스트를 위해서는 안쓰는 USB 케이블을 하나 잘라서 연결을 해야 합니다. 다음은 제가 만들어 붙인 USB 케이블 모습입니다.



이제 DFU를 테스트하기 위한 하드웨어는 다 준비가 됐기 때문에, CUBE-MX 툴로 프로그램을 만들어 보겠습니다.
예전에 쓴 글에 MCU 설정은 많이 나와 있으므로 바로 PINOUT 설정 내용을 그림으로 올리겠습니다.
1. DFU용 USB-FS 핀 2개
2. 외부 CLOCK 입력 핀 : 4개
3. 디버깅용 UART2(Necleo 보드의 JTAG IC에 의해 USB-to-UART 와 연결되어 있다)



클럭 설정은 USB를 사용하기 때문에 USB CLK 를 48MHz로 맞추다 보니, System Clk은 96MHz로 설정된다.
System Clk 에 STM32F411의 최대 클럭인 100MHz 를 입력하면 자동으로 최적의 클럭을 계산해 준다.





기본으로 위와 같이 Disable 로 설정이 안되어 있어서 얼마전에 Custom DFU가 안되는 줄 알고 한참 해멨는데, 주의해야 겠습니다.

 

다음으로 CUBE MX툴의 Config 탭의 USB DEVICE 버튼을 클릭해서 파라메터를 설정합니다.





여기서 눈여겨 볼 내용은 2가지 입니다.
1. USBD_DFU_APP_DEFAULT_ADD : 나중에 업로드할 APP f/w 의 Flash Memory Start Address 를 입력합니다.
Sector 단위로 구분하여 App f/w 를 위치시킬 Sector의 시작 주소를 넣어 줍니다. 저는 Sector 2 부터 사용하려고 0x08008000을 입력했습니다.
2. USBD_DFU_MEDIA Inteface : 부트로더 프로그램의 시작 주소와 각각의 메모리의 Sector 별 특징을 넣어 줍니다.
이 파라메터는 꽤 복잡하게 구성되어 있는데, 해당 입력란을 클릭하면 아래 설명이 나와 있습니다.

USBD_DFU_MEDIA Interface USBD_DFU_MEDIA Parameter Description: The description of the flash (used by PC tool DFuSe) Each Alternate setting string descriptor must follow this memory mapping so that the PC Host Software can decode the right mapping for the selected device: ● @: To detect that this is a special mapping descriptor (to avoid decoding standard descriptor) ● /: for separator between zones ● Maximum 8 digits per address starting by “0x” ● /: for separator between zones ● Maximum of 2 digits for the number of sectors ● *: For separator between number of sectors and sector size ● Maximum 3 digits for sector size between 0 and 999 ● 1 digit for the sector size multiplier. Valid entries are: B (byte), K (Kilo), M (Mega) ● 1 digit for the sector type as follows: – a (0x41): Readable – b (0x42): Erasable – c (0x43): Readable and Erasabled (0x44): Writeable – e (0x45): Readable and Writeable – f (0x46): Erasable and Writeable – g (0x47): Readable, Erasable and Writeable Note: If the target memory is not contiguous, the user can add the new sectors to be decoded just after a slash"/" as shown in the following example: "@Flash /0xF000/1*4Ka/0xE000/1*4Kg/0x8000/2*24Kg"

제가 위의 내용을 토대로 그림으로 좀 정리를 해 봤습니다.



이 파라메터는 나중에 DFU F/W Upgrade 툴인 DfuSe 프로그램에서 읽어서 사용하게 됩니다.
어떻게 사용되는지 미리 알아 보겠습니다.



다음은 DFU 모드로 들어가기위해, 여러가지 조건들을 만들어 넣을 수 있겠지만 Necleo 보드의 User 스위치를 누르면 진입하도록 GPIO 설정에서 핀내부 풀업해 줍니다. (스위치가 누르면 GND와 붙기 때문에)



인터넷에서 DFU 관련 사이트를 찾아보니 HEAP 크기를 늘려 주라고 되어 있었는데, 왜 그런지는 모르겠습니다.
예로써, 0x2000 크기를 Heap으로 잡아 놨던데, 실험 결과 0x800 만 잡아서 문제는 (아직까지) 없었습니다.
Heap 을 늘리면 RAM 의 ZI(Zero Initialize Data)가 늘어 나는 것이 차이라면 차이랄까?



여기까지 CUBE MX툴에서 할 일은 모두 끝났습니다.
다음 단계로 KEIL 컴파일러용 코드를 만들어서 JTAG로 프로그램을 다운로드하고 리셋을 눌러서 부트로더를 실행해서 윈도우의 장치관리자에서 STM Device in DFU Mode 가 뜨면 성공입니다.

아직 프로그램을 추가하고 수정해야 하지만, 이 상태로 STM Device in DFU Mode 가 떠야 그 다음으로 넘어갈 수 있습니다.





회사 일이 많이 바빠서 테스트는 다 했지만 글을 못 올리네요. 약간 설명할 내용이 많아서 시간을 내기가 더 힘든 것 같습니다.
곧 프로그램 수정방법도 올려 보겠습니다.

궁금하신 분은 KEIL 소스코드를 올려 놓을 테니, 미리 보고 공부하셔도 됩니다. ^^


다음의 소스 코드 첨부합니다.


며칠동안 STM32F446 으로 DFU Custom Bootloader 를 열심히 테스트해 봤는데,


결과는 안됐습니다.

STM32F446 은 기본으로 DFU 부트로더가 내장되어 있어서,
BOOT1,BOOT0 을 High로 하고 리셋을 하면 USB로 펌웨어를 다운로드 할 수 있고 동작을 확인해 봤습니다.

그런데 Custom으로 DFU를 구현하는데에는 실패했는데,
STM32F446 이 안되는 것 같습니다.

STM32F407 로 되는 소스를 중국 사이트에서 보고 그대로 했고, 구글 사이트에서도 보고 따라했는데..
다들 문제가 없는 것 같은데 잘 안되네요.

CUBE MX 툴로 프로그램을 하고 있는데, CUBE MX 툴이 다루기는 쉬운데 아직 버그가 많다고 합니다.

지난 번에도 STM32F446 으로 DMA mem to GPIO 를 동작 시키는 데에도 잘 안 됐던 것도 
제 생각에는 CUBE MX 툴이 의심이 갑니다.

다음번에 STM32F411로 한번 해 볼 예정입니다.


역시 CUBE MX 버그인 것 같습니다. 
STM32F411 에서는 똑같은 프로그램인데, DFU 모드로 진입합니다.
아.. 이거 언제 고쳐질런지. (mem-to-GPIO DMA 와 Custom USB DFU)

원인 찾았습니다. 죄송합니다. 아래 댓글에 그동안 안됐던 내용 적어놨습니다.
STM32F446 CUBE MX 툴 이상 없습니다. 내일이나 모레 DFU 부트로더 프로그램에 대한 내용을 다루겠습니다.


STM32F411에서 USB DFU 드라이버가 장치관리자에 설치된 화면은 다음과 같습니다.




+ Recent posts