이번에 CUBEMX 를 업데이트하고 나서, USB CDC 를 테스트 해 봤습니다.


예전에, USBD_CDC.h 파일에서 수정해야 동작하던 상수값이, 
이번 버전(Ver 4.23.0)의 CUBE MX 에서는 제대로 생성해 주는 것을 확인했고,
몇가지 간단하게 수정하면 USB CDC 를 바로 쓸 수가 있었습니다.




usbd_cdc.h  예전의 설정 (USB COM Port 장치 에러 발 생)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** @defgroup usbd_cdc_Exported_Defines
  * @{
  */ 
#define CDC_IN_EP                                   0x81  /* EP1 for data IN */
#define CDC_OUT_EP                                  0x01  /* EP1 for data OUT */
#define CDC_CMD_EP                                  0x82  /* EP2 for CDC commands */
 
/* CDC Endpoints parameters: you can fine tune these values depending on the needed baudrates and performance. */
#define CDC_DATA_HS_MAX_PACKET_SIZE                 512  /* Endpoint IN & OUT Packet size */
#define CDC_DATA_FS_MAX_PACKET_SIZE                 64  /* Endpoint IN & OUT Packet size */
#define CDC_CMD_PACKET_SIZE                         8  /* Control Endpoint Packet size */ 
 
#define USB_CDC_CONFIG_DESC_SIZ                     67
#define CDC_DATA_HS_IN_PACKET_SIZE                  CDC_DATA_HS_MAX_PACKET_SIZE
#define CDC_DATA_HS_OUT_PACKET_SIZE                 CDC_DATA_HS_MAX_PACKET_SIZE
 
#define CDC_DATA_FS_IN_PACKET_SIZE                  CDC_DATA_FS_MAX_PACKET_SIZE
#define CDC_DATA_FS_OUT_PACKET_SIZE                 CDC_DATA_FS_MAX_PACKET_SIZE
cs


usbd_cdc.h  새로 수정된 내용(CDC_DATA_HS_MAX_PACKET_SIZE 값이 256 으로 초기설정됨)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** @defgroup usbd_cdc_Exported_Defines
  * @{
  */ 
#define CDC_IN_EP                                   0x81  /* EP1 for data IN */
#define CDC_OUT_EP                                  0x01  /* EP1 for data OUT */
#define CDC_CMD_EP                                  0x82  /* EP2 for CDC commands */
 
/* CDC Endpoints parameters: you can fine tune these values depending on the needed baudrates and performance. */
#define CDC_DATA_HS_MAX_PACKET_SIZE                 256  /* Endpoint IN & OUT Packet size */
#define CDC_DATA_FS_MAX_PACKET_SIZE                 64  /* Endpoint IN & OUT Packet size */
#define CDC_CMD_PACKET_SIZE                         8  /* Control Endpoint Packet size */ 
 
#define USB_CDC_CONFIG_DESC_SIZ                     67
#define CDC_DATA_HS_IN_PACKET_SIZE                  CDC_DATA_HS_MAX_PACKET_SIZE
#define CDC_DATA_HS_OUT_PACKET_SIZE                 CDC_DATA_HS_MAX_PACKET_SIZE
 
#define CDC_DATA_FS_IN_PACKET_SIZE                  CDC_DATA_FS_MAX_PACKET_SIZE
#define CDC_DATA_FS_OUT_PACKET_SIZE                 CDC_DATA_FS_MAX_PACKET_SIZE
cs

그래서 수정할 내용은, 
1. #include 에서 usbd_cdc_if.h 추가하고,
1
2
#include "string.h"
#include "usbd_cdc_if.h"
cs

2. 저는 보통 cdc를 디버그 출력용으로만 사용해서 Txd 기능만 주로 사용합니다.
  - 그리고, 쓰기 편하게 printf() 함수를 사용하다 보니, string.h 파일을 include 해 줍니다.
    USB_FS 로  printf() 함수를 사용하기 위한 추가 코드는 다음과 같습니다. main.c 파일의 User code 에 추가해 주면 됩니다.
 main.c 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 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_FS((uint8_t *)&ch, 1== USBD_BUSY);
 return ch;
}
 
/* USER CODE END 0 */
cs
  - JTAG 나 SSTLINK 디버거용 핀을 보드에 배치하면 보드 크기가 커져서, 저는 USB CDC를 디버깅 기능으로 사용합니다.
  - USB 만 연결해서, 프로그램 다운로드는 USB DFU 를 사용하고, 디버깅이 필요할 시 USB CDC 를 사용하면 편리합니다. ^^

3. 위 설정만 완료하면, USB COM Port 로 PC 와 통신하면 됩니다.




다음은 간단한 USB CDC 출력 테스트 프로그램입니다.
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
82
83
84
85
86
87
88
89
90
/* 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 */
 
/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;
 
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
 
/* USER CODE END PV */
 
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
 
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
 
/* USER CODE END PFP */
 
/* 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_FS((uint8_t *)&ch, 1== USBD_BUSY);
 return ch;
}
 
/* 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();
 
  /* 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_I2C1_Init();
  MX_USB_DEVICE_Init();
 
  /* USER CODE BEGIN 2 */
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        printf("USB CDC Test ^^\r\n");
        HAL_Delay(500);
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
 
  }
  /* USER CODE END 3 */
 
}
cs


터미날 프로그램으로 테스트한 내용입니다.



alpu_v100.zip


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


안녕하세요,


요 근래, 파일 시스템 테스트 하다가 안되서 고생고생 했네요.

그 고생한 내용은 다음과 같습니다.

파일 관련 함수는 거의 다 테스트를 했는데, f_read() 함수만 유독 실행이 될 때도 있고, 
어떤 때는 계속 실행이 안되고 Hardfault Error 인터럽트로 점프해서 멈춰 있었습니다.

같은(거의 비슷한?) 입력 파라메터를 갖고 있는 f_write() 함수는 또 잘 된다.
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from a file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to a file */

하~.., 그래서 사용자 함수를 하나 만들어서 파라메터를 f_read() 와 같이 정의를 해서 테스트를 해보니 f_read()함수와 같은 증상이 발생. ㅜㅜ

안되는 원인을 찾아 보려고 f_read()함수의 파라메터의 입력을 각각 1가지씩 사용자 함수에 적용해서 테스트를 해 봤더니,
입력 인자 중 void* buff 가 문제였습니다.

원인은 입력 인자인 배열(void *buff)을 f_write()에서 보다 많이 잡아서 Stack overflow 가 걸리는 것이었습니다.

에러가 나면 스택 오버플로우 인지 확인도 못하고(혹시 아시는 분 있으시면 방법을 알려 주시면 감사하겠습니다),
그냥 프로그램이 멈춰버리니.. 며칠을 밤새고 나서야 찾을 수 있었습니다.

이번 글의 요점은, 파일 시스템 관련 함수를 사용할 경우에는 스텍을 충분히 확보하라 입니다.

그럼, 다음은 스텍을 늘리는 방법입니다. 방법은 2가지가 있네요.
1. startup_stm32f411xe.s 파일을 열어서 다음과 같이 스택 사이즈를 변경합니다.



2. CUBE 툴에서 project > Setting 메뉴에서 수정할 수도 있습니다. (저는 큐브 툴로 KEIL 소스를 만들어서 이런 방법도 있음을 알려 드립니다)



스택이 0x400 일 때 멈춰버린 프로그램이..



스택을 0x800 으로 고치니까 아주 잘 됩니다. 잘 되서 f_lseek()함수도 사용해서 파일의 offset를 막 이동해 가면서 읽어서 UART로 마구마구 뿌려 댔습니다. ^^



그런데, 나중에 알게된 사실인데... 

큐브 툴의 Help 메뉴를 누르면 UM1718.pdf 파일이 열리는데, 이 곳의 7번의 제목의 글에 스택을 충분이 할당하라는 내용이 약간 나옵니다. ㅜㅜ 저와 같은 문제로 고생하는 사람이 많진 않지만 약간 있나 봅니다.
많았으면 반드시 스택을 많이 잡고 시작하라고 했을 텐데.





그리고 이 PDF의 파일 시스템 예제가 인터넷에 돌아다니는 예제 보다, 가장 잘 나온 것 같습니다.
참고하시면 도움이 될 것 같습니다.


이번에는 SPI 통신을 테스트 해 봤습니다.


원래는 WIFI 모듈을 연결하여 테스트를 진행하려고 했는데, 
회사에서 WIFI 모듈은 잠깐 미뤄두고 급한 일을 먼저 처리하라고 해서..
IC를 연결하지 않고 SPI Write만 동작시키고, 파형만 오실로 스코프로 찍어 봤습니다.


Nucleo 보드의 SPI 용 핀 할당은 다음과 같습니다.



STM32F411 의 SPI용  핀할당 내용입니다.
SPI_CS 는 일반 GPIO로 제어했습니다.



아무 설정도 안하고 기본 SPI 스피드는 42MHz 입니다. Cube 툴에 표시 되네요.



다음은 SPI_CS 를 일반 GPIO 출력으로 설정하는 코드 예제입니다.
PA8 핀을 사용했습니다. 설정은 내부 풀업,출력,Fast Speed,초기값 HIGH  입니다.



기본 SPI_CLK 스피드인 42MHz 로 출력을 테스트 해 보니, 오실로 스코프가 파형을 못 쫒아가서 파형이 엉망으로 나오네요.



다음은 42MHz SPI CLK 확대한 그림입니다. 스코프가 못 쫒아갑니다. 오실로 스코프 회사에 연락해 보니, 측정할 클럭의 10배는 되야 보인다 하더군요. 42MHz 이면 500MHz 오실로 스코프가 필요하단 말이네요. 제가 갖고 있는 건, 200MHz 입니다.



다음은 SPI_CLK를 21MHz로 설정한 소스 코드입니다. 



이 코드로 동작시킨 21MHz SPI_CLK 과 파형입니다.



그리고 다음은 21MHz SPI_CLK 확대 파형입니다. 클럭이 잘 보이네요.



그런데 이상한 점은 42MHz 나 21MHz 나 통신 속도가 똑같다는 점입니다.
42MHz가 전송 속도는 21MHz 보다 2배는 빨라야 하는데, 10 Byte 전송 테스트시 같은 시간이 걸린다는 게 좀 의아하네요.

좀 전에, 스코프 회사에서 점검하고 갔는데.. 아무리 해도 개선이 안되네요.
금요일에 300MHz 오실로 스코프를 가지고 온다니까, 스코프가 문제인지 파형이 문제인지 확인할 수 있겠습니다.


DMA 나 인터럽트로 동작시켜야 SPI_CLK 사이의 구간이 짧아 지려나? 
현재는 회사 다음 일정이 바빠서 인터럽트나 DMA 테스트는 나중에 진행하겠고, 
테스트 후에 자료를 다시 올리겠습니다.





테스트 한 소스코드 첨부합니다.

SPI_test.zip


STM32F4xx 의 SDIO 통신방식으로 u-SD Card 를 다뤄 보겠습니다.


여러가지 예제를 보고 그대로 했는데도 자꾸 실수를 하는 바람에,
동작 시키기까지 하루 종일 걸렸네요. ^^

SD 카드 모듈은 Waveshare 에서 만든 그냥 u-SD Card 모듈인데, 엘레파츠에서 샀습니다.



Nucleo 보드와 SD Card 모듈의 H/W 연결은 다음과 같습니다.



실제 연결한 그림입니다. 사진을 잘 못 찍어서 이렇네요. ^^



SDIO의 큐브에서 설정은 다음과 같습니다. SDIO 를 넣었더니 clock 설정에서 APB1 peripheral clocks(MHz) 가 42MHz 이하로 줄여야 한다고 에러가 나서 42MHz로 맞추다 보니 시스템 클럭은 84MHz로 줄어 버렸습니다.



SDIO 4Bit 핀아웃은 다음과 같습니다. 데이터버스 4개,CLK,CMD 로 총 6개의 I/O 가 사용되고 경우에 따라서 CD(Card Detect) 입력을 1개 쓸 수도 있습니다. 일단은 테스트니까, CD는 사용 안했습니다.



프로그램은 그냥 Polling으로 처리했습니다. 나중에 DMA를 사용하면 아주 빠르다고 하네요.
프로그램은 
1. SD card 초기화하고, 
2. 텍스트 파일을 1개 만들어서 , 
3. 파일에 스트링을 몇자 쓰고 ,
4. 파일을 닫아서,
5. PC에서 SD Card를 읽어서 확인해 봤습니다.

다른 것은 별 어려움이 없었는데, 4Bit Wire로 바꾸는 것이 좀 어려웠습니다.
어딘가에서 바뀔 수도 있는지 모르겠는데, BSP_SD_Init(); 함수를 실행해야 4비트 방식으로 바뀌는 것인데..
프로그램 어디에도 BSP_SD_Init();함수를 실행하지를 않아서 SD Card 초기화 하고 나서 main()함수안에 BSP_SD_Init(); 함수를 실행하도록 수정했습니다.

int main(void)
{

  /* USER CODE BEGIN 1 */
    uint8_t i;
    char tx_buf[100];
    uint8_t file_path[13] = "testfile.txt";
    file_path[12] = '\0';
    uint8_t testBuffer[16] = "SD write success";
    UINT testBytes;
  /* 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_DMA_Init();
  MX_TIM3_Init();
  MX_USART2_UART_Init();
  MX_I2C1_Init();
  MX_SDIO_SD_Init();
  MX_FATFS_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();

  /* USER CODE BEGIN 2 */
for (i=0;i<10;i++)
{
sprintf(tx_buf,"UART Test ... [%3d] \n\r",i);
HAL_UART_Transmit(&huart2,(uint8_t *)tx_buf,cus_Size_of(tx_buf),3);
}
HAL_TIM_Base_Start_IT(&htim3);

BSP_SD_Init();

   :
   :
   :
}




다음은 이 프로그램에 의해 UART로 response 를 출력해 본 내용입니다. (제가 디버그용으로 UART를 주로 쓰는 편이라..)
response 가 0 이면 정상입니다.



다음은 SD card 내용과 그안에 만들어진 텍스트 파일의 내용입니다.





소스파일 첨부 합니다.


이번에는, 이전 소스 코드에, UART2 인터럽트 처리 함수를 추가해 봤습니다.


STM32F4xx 의 UART 데이터 처림 방식에는 3가지가 있습니다.
1. Polling mode
 보내고 받는 동작에서 다른 일을 못하고 상태를 지켜 보고 있어야 해서 효율이 떨어진다.
2. Interrupt mode
 보내고 받는 동작이 완료되면, 인터럽트 함수로 점프해서 특정 동작을 처리하고 돌아온다.
보낼 경우는 몇개의 데이터를 보내는지 알아서 딜레이를 주던가, 주기적으로 상태를 체크하면서 동작 시키면 되는데,
받을 경우에는 가변 길이의 프로토콜일 경우 1개의 데이터가 받아질 때마다 인터럽트 처리를 해야 하는데 
데이터 전송률이 높을 수록, 또 수신 데이터가 많아질 수록 함수를 자주 호출하므로 다른일을 못하게 되어 효율이 떨어진다.
3. DMA mode
가장 효율이 좋은 방식으로, 인터럽트 처리 함수를 호출할 필요가 없이 메모리에 직접 데이터가 받아지므로,
주기적으로 받은 데이터를 링버퍼에 쌓아서 처리하면 된다.
외국에서 DMA방식을 써서 많은 잇점이 있다고 하는 글을 첨부해 봅니다.

DMA allows the UART to store data in memory WITHOUT interrupting the processor until the buffer is full saving lots of overhead.

In my case, I told PDC to store UART data into an buffer (byte array) and specified the length. When UART via PDC(Peripheral DMA controller) filled the buffer the PDC issued an interrupt.

In PDC ISR:

  1. Give PDC new empty buffer
  2. Restart UART PDC (so can collect data while we do other stuff in isr)
  3. memcpy full buffer into RINGBUFFER
  4. Exit ISR

As swineone recommended above, implement DMA and you'll love life.


저는 DMA는 링버퍼 구현 때문에 시간이 좀 걸릴 것 같아서, 
우선은 다른 구현해야할 동작을 먼저 하고 일단은 RX 인터럽트만 동작을 시켜봤습니다.

이전에 Cube툴에서 UART2 인터럽트를 설정해 놓고 코드를 생성했기 때문에,
이전 글에서 첨부한 소스코드에 Rx 인터럽트 처리함수만 추가해서 1바이트 받으면 바로 TX로 뿌려주는 동작만 구현했습니다.

타이머 인터럽트 처리함수처럼 UART인터럽트도 사용자 콜백함수가 있었습니다.
이 함수 역시도 __weak 키워드가 붙어 있고 이것을 사용하려면 사용할 사용자 파일에 복사해서 붙여넣기를 해서
__weak 키워드를 지워주고 함수 안의 내용을 입맛에 맞게 고쳐주면 됩니다.

처음에 할 일은, 설정은 이미 자동으로 코드가 생성되어 완료가 됐고 
1. "내가 uart2 포트(huart2)로 인터럽트 방식을 사용해서 특정 변수에(uart2_rx_ch) 1바이트를 받겠다" 라는 명령을 실행시킵니다.
 HAL_UART_Receive_IT(&huart2,&uart2_rx_ch,1);

2. 위의 코드가 실행되면 uart2로 1바이트의 데이터가 수신될 때마다 인터럽트가 걸리는데,
이 때마다 위의 설명에서 말씀드린 사용자 콜백함수로 점프해서 해당 코드를 실행하고 복귀합니다.
아까 말씀 드린 stm324xx_hal.c 파일에 있던 __weak 키워드가 붙어있는 사용자함수를 main.c 파일에 붙여 넣고 함수 내부 코드를 수정한 함수는 다음과 같습니다.



코드의 내용은 "받은 데이터를 폴링방식으로 TX로 출력하고, 다시 인터럽트로 1바이트 받겠다" 입니다.
일종의 echo 동작이죠.
DMA 까지 필요없는 분들은 이 함수를 수정해서 링버퍼에 쌓아서 쓰시던가 그냥 입맛에 맞게 적당히 고쳐서 쓰시면 되겠습니다.

테스트 결과는 다음과 같습니다.



소스코드 첨부 합니다.

timer3_test2.zip




어제부터 Cube 툴을 사용하다가, 새로 알게된 것이 있어서 또 글을 씁니다.

Cube 사용법에서 빼 먹었다고도 할 수 있겠네요.

어제까지 동작 시킨 내용이 알고 보니,
내부 RC 클락 8MHz로 동작 시킨 것이었습니다.

원래의 의도는 ST-Link 디버거 칩으로부터 8Mhz 클락을 받아서, STM32F411 에서 PLL로 최대 클럭인 100Mhz로
동작 시킬려고 했었는데 경험 미숙으로 실수를 했습니다.
Cube 툴에 선택을 할 수 있는 Tab이 여러개 있었는데, 못봤네요.

먼저, 기본으로 표시되는 Pinout tab 에서 외부 클럭을 입력 받을 수 있도록 RCC 설정을 바꿔 주면,
Pinout 그림에 PC14,PC15,PH0,PH1 핀이 사용됨으로(녹색 표시) 표시됩니다.
그리고 저는 나중에 UART2,Timer3 를 사용할 수 있도록 설정했습니다.



Clock Configuration tab을 선택해서 클럭을 100MHz에 가까이(96MHz) 외부클락과 PLL을 사용해서 수정해 봤습니다.



이렇게 해서 코드를 출력해서 KEIL 컴파일러에서 설정 버튼을 눌러서 보니, 클럭이 96.0MHz로 변경이 되어 있었습니다.
이게 맞는 건지? 나중에 테스트를 해 보면 알겠죠 뭐. ^^



클럭이 제대로 맞는지 보기 위해서 타이머 인터럽트를 발생시켜서 Nucleo 보드의 눈으로 볼 수 있는 유일한 출력인 LED를 동작시켜 보기로 했습니다.
다시 Cube 툴로 가서 Configuration tab을 선택하면 블럭도가 있는데, 그 중에서 TIM3 를 마우스로 클릭합니다.
그러면, 팝업창이 하나 뜨는데 Parameter Setting 에서 Prescaler 값을 1000으로, Counter Period 를 1000 으로 설정하고
다시 KEIL MDK 코드를 만듭니다.



만들어진 KEIL MDK 소스 중, main.c 파일의 MX_TIM3_Init() 함수에 Cube 툴에서 설정한대로 코드가 생성된 것을 볼 수 있습니다.
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
/* TIM3 init function */
static void MX_TIM3_Init(void)
{
 
  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;
 
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 1000;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 1000;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
 
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
}
 
cs

이후에 Timer3 Interrupt 가 동작이 안되서 반나절 헤맸습니다.
Cube 툴로 생성된 코드는 딱 초기화까지만 만들어지고, 그 이후는 사용자가 추가하거나 수정해서 동작시켜야 했습니다.
인터넷도 찾아보고 이것 저것 코드도 수정하다가 요행으로 동작이 됐습니다.
동작이 안된 원인은 , 타이머를 인터럽트 모드로 동작 시작을 안 한 것입니다.
main()함수에 HAL_TIM_Base_Start_IT(&htim3);를 추가해 주면 됩니다.
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
int main(void)
{
 
  /* USER CODE BEGIN 1 */
    uint8_t i;
    char tx_buf[100];
    
  /* 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_TIM3_Init();
  MX_USART2_UART_Init();
 
  /* Initialize interrupts */
  MX_NVIC_Init();
 
  /* USER CODE BEGIN 2 */
        for (i=0;i<10;i++)
        {
            sprintf(tx_buf,"UART Test ... [%3d] \n\r",i);
            HAL_UART_Transmit(&huart2,(uint8_t *)tx_buf,cus_Size_of(tx_buf),3);
        }
 
        HAL_TIM_Base_Start_IT(&htim3);
        
  /* 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

그런데, 인터럽트 처리 함수는 어딨을까요? 
이것도 좀 헤멨는데.. 그래도 timer start 함수를 알아내는 것 보다는 좀 덜 걸렸습니다.
stm32f4xx_it.c 파일 안에 TIM3_IRQHandler() 함수를 수정하면 됩니다.
이 함수 안에 LED 포트를 토글하도록 코드를 수정했습니다.

(글 작성한 다음 날 정확한 사용 법을 알게 되었습니다. 이 내용은 동작은 되지만 제대로된 사용법이 아니여서 수정합니다.)



위의 코드에서는 타이머 인터럽트가 걸리면 TIM3_IRQHandler() 함수를 콜해서 내부 내용이 실행되기는 함니다.
하지만, 이렇게 하면 HAL_TIM_IRQHandler() 함수의 의미가 없게 됩니다.
HAL_TIM_IRQHandler() 함수를 살펴 보니 Timer 인터럽트도 여러가지 조건이 있고 그 조건에 따라 특정 함수를 호출하는 구조로 되어 있었습니다.
제가 설정한 조건은 Timer Complete 시 인터럽트가 걸리는 것 인데, 그 때 HAL_TIM_IRQHandler() 함수 내부에서 호출되는 함수는 HAL_TIM_PeriodElapsedCallback(htim); 이었습니다.

HAL_TIM_PeriodElapsedCallback() 함수는 stm32f4xx_hal_yim.c 파일에 위치해 있는데, 그 함수 앞에__weak 라는 키워드가 정의되어 있고 함수 안에 사용 방법에 대한 설명이 자세히 적혀 있습니다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
  * @brief  Period elapsed callback in non blocking mode 
  * @param  htim: pointer to a TIM_HandleTypeDef structure that contains
  *                the configuration information for TIM module.
  * @retval None
  */
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(htim);
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
}
cs

즉, 여기 함수는 건드리지 말고, 필요한 경우에 사용자 파일에 이 코드를 넣어서, 함수 안의 내용을 정의해서 사용해라.. 입니다.
그래서 저의 경우, main.c 파일에 __weak 키워드를 제거하고 HAL_TIM_PeriodElapsedCallback()함수를 카피해서 안의 내용을 LED 포트를 토글하도록 수정하니, 예전처럼 동작이 되네요.




1
2
3
4
5
6
7
8
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* Prevent unused argument(s) compilation warning */
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
}
cs

이렇게 수정한 내용으로 소스 파일을 다시 첨부합니다. 아직 KEIL 컴파일러는 서툴러서 실수를 했네요. 죄송합니다.


그리고 컴파일러에서 빌드를 하고 프로그램 다운로드까지 한 후, 리셋 버튼을 눌러 주면 프로그램이 동작하기 시작합니다.
LED가 계속 켜져 있어서, 그냥 오실로 스코프로 찍어 보니 출력이 10.5ms 마다 인터럽트가 걸리고 있음을 알 수 있었습니다.




제가 Cube에서 prescaler를 1000, period를 1000으로 설정했으므로 실제 Timer3의 source clock은 
현재 인터럽트 출력 주파수인 100Hz에 x1000 x1000 을 해 주면 100MHz가 됩니다.
주기를 10ms로 계산했는데, 좀 더 정확하게 10.5ms로 계산하면 약 96MHz가 나오니 클럭이 제대로 PLL에 의하여 돌아가는 것을 알 수 있었습니다.

테스트한 KEIL MDK 소스 파일 첨부합니다.


이번에는 GPIO 출력 방법에 대해서 알아 보겠습니다.


GPIO 출력을 위해서는 먼저 GPIO 를 초기화 해야하는데, 
STM 계열의 문법은, 먼저 다음과 같은 구조체로 GPIO 변수를 선언하는 군요.

main.c 파일에서 global 구조체 함수로 다음과 같이 정의를 합니다.
static GPIO_InitTypeDef  GPIO_InitStruct;

GPIO_InitTypeDef 라는 구조체 형식은 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** 
  * @brief GPIO Init structure definition  
  */ 
typedef struct
{
  uint32_t Pin;       /*!< Specifies the GPIO pins to be configured.
                           This parameter can be any value of @ref GPIO_pins_define */
 
  uint32_t Mode;      /*!< Specifies the operating mode for the selected pins.
                           This parameter can be a value of @ref GPIO_mode_define */
 
  uint32_t Pull;      /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
                           This parameter can be a value of @ref GPIO_pull_define */
 
  uint32_t Speed;     /*!< Specifies the speed for the selected pins.
                           This parameter can be a value of @ref GPIO_speed_define */
 
  uint32_t Alternate;  /*!< Peripheral to be connected to the selected pins. 
                            This parameter can be a value of @ref GPIO_Alternate_function_selection */
}GPIO_InitTypeDef;
cs

제가 갖고 있는 Nucleo 보드에서 LED가 PA5 에 연결되어 있어서 핀을 출력으로 설정해 봤습니다.
main.c 파일 내에, uart 테스트에 사용했던 프로젝트에서 사용된 초기화 함수에 PA5 설정에 대한 코드를 추가했습니다.
다음의 빨간 색으로 된 부분이 추가된 코드입니다. 

__HAL_RCC_GPIOA_CLK_ENABLE() 함수는 원래 부터 uart 테스트 프로젝트에 있던건데 아마도 Uart 포트가 PA2,3 이라 포트를 사용하기 위해서 Clock Enable이 필요한 듯 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** Pinout Configuration
*/
static void MX_GPIO_Init(void)
{
 
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
 
  /* -2- Configure PA05 IO in output push-pull mode to
         drive external LED */
  GPIO_InitStruct.Pin = GPIO_PIN_5;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 
}
 
cs

다음으로 main()함수에 출력으로 설정된 LED GPIO 를 Toggle 하고 딜레이 함수로 100ms 기다리고를 반복하는 코드를 추가했습니다.
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
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();
 
  /* USER CODE BEGIN 2 */
 
    HAL_UART_Transmit(&huart2,"UART2 Test~!!! \n\r",17,0xFFFF);
//HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
    
    /* Insert delay 100 ms */
    HAL_Delay(100);
 
  }
  /* USER CODE END 3 */
 
}
cs

자동으로 리셋이 되지 않아서, 프로그램이 잘 안됐나 했는데...
리셋 버튼을 눌러주니 동작하기 시작하네요.

uart2 테스트와 gpio 테스트 파일을 첨부(공유)함니다.


안녕하세요,


이번에는 STM32 ARM 시리즈를 사용해야할 상황이 되었습니다.
STM32F411 이 패키지가 3x3 mm 크기인 WLCSP 타입이면서, SDCARD I/F 인 SDIO 포트가 구비되어 있어서
이 MCU를 사용하게 되었습니다.

PSOC 처럼 쉽지는 않아서 익숙해지기 까지는 좀 시간이 걸릴 것 같습니다.


Nucleo 개발 키트를 구매했는데, UART를 테스트 해 보려고 회로도를 보니
디버거와 USB-UART 가 붙어 있어서 USB-UART에 해당하는 UART 포트를 Cube(STM에서 제공하는 툴)로
설정하고 코드를 자동 생성하도록 했습니다.

STM의 Cube라는 툴은 컴파일러가 포함되어 있는 툴이 아니고, 
여러 Peripheral 기능을 선택해서 설정만 해 주면, 해당 기능의 소스 코드를 여러가지 컴파일러(IAR,KEIL,GCC..etc)에
맞게 자동 생성해 주는 기능을 가지고 있습니다.

그런데, 사용 예제는 생성해 주지 않고 초기화까지만 해줘서 어떻게 써야 하는 건지 좀 해깔렸는데,
다행이 STM 홈페이지에서 첨부된 예제를 보고 감을 잡을 수 있었습니다.

먼저 Cube 사용법을 알아 봅시다.
1. Cube 를 실행 시켜서 File->New Project 를 클릭.
2. 팝업 창이 하나 뜨는데, 사용할 STM MCU를 선택합니다.


3. 사용할 Peripheral 기능을 선택합니다. 저는 UART 테스트를 위해 일단 UART만 선택했습니다.
Nucleo 보드에서 USB-to-UART 에 연결된 STM32F411 의 포트는 PA2,PA3 이었습니다.
그래서 따로 USB-to-UART 컨버터를 준비하지 않고 테스트하려고, Cube 에서 PA2,PA3에 해당하는 UART를
찾아 보니 UART2 였습니다.


4. 현재 갖고 있는 컴파일러에 맞는 코드를 생성합니다.


5. 팝업창이 뜨는데, 다음의 그림과 같이 프로젝트 이름,위치,컴파일러 종류를 선택하고 OK를 누르면 해당 위치에 소스코드가 만들어 집니다. 저는 KEIL 컴파일러를 사용해서 MDK-ARM V5로 선택했습니다.


6. 다음의 과정으로 팝업창이 뜨는데, OK를 누르면 자동으로 컴파일러와 연계되어 KEIL 컴파일러가 실행됩니다.


KEIL 의 main()함수에 UART 출력 함수를 써서 테스트를 했습니다.
STM ARM 칩은 처음이라, 함수 사용법이 좀 생소하네요. 일단 UART 출력 코드는 다음과 같이 1줄 추가했습니다.

7. teraterm 에서 확인한 내용입니다.







+ Recent posts