페이지

글목록

레이블이 USB인 게시물을 표시합니다. 모든 게시물 표시
레이블이 USB인 게시물을 표시합니다. 모든 게시물 표시

2016년 12월 14일 수요일

[STM32F4xx] Custom 보드 테스트 #23 (USB CDC.STM32F446 : KEIL)

이번에는 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

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

2016년 11월 30일 수요일

[STM32F4xx] Nucleo 보드 테스트 #22 (DFU Bootloadable APP프로그램 : KEIL)

일반적인 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 프로그램이 실행된다.

[STM32F4xx] Nucleo 보드 테스트 #21 (DFU Bootloader 프로그램 수정 : KEIL)

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

지난 번에 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