리셋 시, BOOT0 핀과 BOOT1(PB2) 핀은 조합되어서, Booting mode 를 결정하는데 사용한다.


그런데, 리셋 이후의 BOOT0/BOOT1(PB2) 의 기능은?

테스트를 해보겠습니다.

공간의 절약을 위해 스위치를 BOOT0 과 GPIO Input 핀을 같이 연결해서,
스위치를 누르고 리셋을 하면 BOOT0 이 1이 되어 DFU 모드로 동작하고,
리셋 이후에 스위치를 누르면 일반 GPIO Input 기능으로 동작을 하도록 H/W를 구성해서 테스트를 해 봤습니다.

다음이 H/W 구성도 입니다.




바로 전에 쓴 글에서 LED OUT 을 사용해서 리셋 이후, BOOT1(PB2) 을 GPIO OUT으로 사용할 수 있음은 증명이 됐고,

다음은 리셋 이후, BOOT0 과 연결된 GPIO Input 핀에 연결된 스위치를 누르면서 상태를 읽어서,
USB CDC 로 뿌려 본 동영상입니다.



동영상을 보시면 리셋 이후, GPIO Input 핀과 연결된 BOOT0 핀이,
스위치에 의해서 아무 기능을 하지 않는다는 것을 알 수 있습니다.

결론은, BOO0,BOOT1(PB2) 은 리셋 시에만 영향을 미치고, 리셋 이후에는 아무런 기능도 하지 않는다.


아하 이런, 제목이 좀 잘못된 것 같네요. 하지만 Nucleo 보드에서도 적용 가능하니 놔둡니다.
(회사에서 만든 보드니 커스텀 보드 테스트라고 해야 했나? ^^)

[DFU f/w 업로드 과정]

- 간단한 기능의 S/W 를 컴파일해서, 

- ST 에서 다운 받은 응용프로그램(DfuSe 툴: 링크)으로,
- Embedded DFU 모드용 파일로 변환하고, 
- USB 로 MCU에 업로드하는 방법을 알아보겠습니다.

테스트하고 있는 MCU는 STM32F411CCYx(FLASH 256KB / SRAM 128KB / WLCSP) 입니다.
프로그램은 TIM2 Interrupt 에 의해 500ms 마다 LED 와 연결되어 있는 GPIO 를 토글해서 눈으로 동작을 확인할 생각입니다.

TIM2 프로그램은 예전에 올렸던 글을 참조하셔서 작성하면 됩니다.
STM32F446 으로 작성된 내용인데, 클럭만 틀리고 다 같습니다.

혹시 링크타고 가서 보는 것이 번거로울 것 같아서, 다시 CUBEMX 에서 바꿀 부분만 설명 드리자면,
TIM2 의 클럭 소스를 확인해서, (이전글 참고 : [STM32F4xx] Timer Clock source )





CUBEMX 툴의 TIM2 설정에서 0.5ms 에 해당하는 주기를 넣어 준다.




다음으로 CUBEMX에 의해 소스코드를 만들어 주고(저는 KEIL 컴파일러를 사용하므로 KEIL v5로 선택을 했습니다.)
TIM2 인터럽트에 의해 500ms 마다 GPIO를 토글하는 코드를 추가해 줍니다.
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
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* Prevent unused argument(s) compilation warning */
    if (htim->Instance == TIM2)
    {
        HAL_GPIO_TogglePin(LED_OUT_GPIO_Port,LED_OUT_Pin);
    }
    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
   */
}
/* USER CODE END 0 */
 
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_TIM2_Init();
  MX_USB_DEVICE_Init();
 
  /* USER CODE BEGIN 2 */
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  HAL_Delay(500);
  HAL_TIM_Base_Start_IT(&htim2);
    
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
 
  }
  /* USER CODE END 3 */
 
}
cs


DFU 로 프로그램을 STM32F4xx 에 업로드 하기 위해서는 HEX 파일이 필요하므로, 
KEIL 컴파일러의 설정에서 다음과 같이 Create HEX File 옵션을 선택해 준다.





다음 과정은 예전에 Custom DFU 에서 설명했던 내용과 똑같은데, 
ST 홈페이지에서 DFU File Manager 를 다운 받아서 실행해서 다음과 같이 입력하고,
S19 or HEX 버튼을 눌러서 생성된 HEX 파일을 선택해 주고 DFU 파일을 원하는 위치에다 만들어 줍니다.




다음으로 DfuSe Demo 프로그램을 ST 사에서 다운 받아서 실행시키고,
BOOT0 을 VCC 로 연결하고 리셋을 합니다(제 경우에는 스위치를 BOOT0 와 VCC 사이에 달아 주고, 스위치를 누른 상태에서 전원을 넣어 줍니다), 
STM Device in DFU Mode 드라이브가 생기면서 DfuSe Demo 프로그램에 해당 칩 정보가 표시됩니다.
DfuSe Demo 프로그램에서 다음과 같이 순서대로 실행하시면 프로그램이 Upgrade 된 후, 실행이 됩니다.
1. Choose 버튼을 눌러 DFU File Manager 에서 만들어 놓은 DFU 파일을 선택한다.
2. Upgrade 버튼을 눌러서 프로그램을 MCU 에 Upload 한다.
3. Leave DFU mode 버튼을 누르거나, BOOT0 핀을 VCC와 연결을 끊고 리셋을 하면 프로그램이 실행됩니다.  





[프로그램 실행 동영상]


이번에는 아주 간단한 DFU(device firmware upgrade) 모드를 테스트 해 보겠습니다.


예전에 올렸던 내용은 System memory에 들어 있는 boot loader를 사용하지 않고,
boot loader 를 직접 만들어서 동작하는 Custom mode DFU를 소개했었습니다.

이번에 설명 드리는 DFU 는 이미 칩생산 시에 들어가 있는 부트로더를 사용하는 것으로 embedded mode DFU 라고도 부릅니다.

여러가지 통신 I/F 를 선택해서 부트로더를 사용할 수 있는데, 
저는 USB 포트를 사용하면 가장 간편하고 빨라서 DFU를 선택했습니다.

[embedded mode bootloader 종류]





부트로드 모드로 들어가기 위해서는 System memory 에 있는 부트로더 프로그램이 실행되도록,
RESET 시 , BOOT0/BOOT1 핀을 다음과 같이 유지시켜 줘야 한다. (리셋 이후에는 필요없음)
1. BOOT0 핀 : 1 (HIGH)
2. BOOT1 핀 : 0 (LOW)





Nucleo 보드로 테스트를 해 봤는데, 
BOOT1 핀은 기본으로 리셋 시 0 으로 인식을 하므로 아무것도 연결 안했고,
BOOT0 만 VDD 와 점퍼로 Short 시켰다.
그리고 이미 전원이 연결되어 있으므로 USB 케이블은 USB DP,USB DM, GND 3선만 연결했다.





[Nucleao 보드 사진]




[USB 케이블 연결 사진]





참고 : PC 쪽 USB 포트 선 결선 정보.





이렇게 하드웨어를 구성했으면, 리셋 버튼을 눌러 주자.
제대로 동작을 한다면, 다음과 같이 PC 장치 드라이브에 드라이버가 설치될 것이다.




다음 번에 hex 파일을 올리는 것을 간단하게 설명 드리겠습니다.

DFU가 SLINK 로 프로그램하는 것보다는 번거롭지만, 
어디를 가든 PC 1대만 있으면 USB 포트로 업데이트가 가능하니 편리합니다.


일반적인 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