어제부터 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