콘텐츠로 건너뛰기

NUCLEO-STM32F411RE와 STM32CubeIDE

  • 컴퓨터와 NUCLEO 보드와의 연결과 통합개발환경을 지원하는 STM32CubeIDE는 인스톨 된 상태이다.

 Board Selector 패녈에서 Commercial Part Number에 411RE를 입력한 후 다이얼로그 박스에서 선택한다.

조금 시간이 걸린다. 

위에서 회색이 아닌 색이 입혀진 핀들은 사용되는 핀들이다. 동일 색은 동일 그룹에 속한다. 핀 하나 씩 눌러서 확인해보자. 그리고 Clock Configuration 패널을 연다.

두 개의 클록이 들어가고 있는 형태이나 내부 클럭을 사용하고 있다.

HSI와 LSI는 내부의 클럭이다. HSE(High Speed External)와 LSE(Low Speed External)이 외부에서 클럭을 공급한다.

System Core의 RCC(Reset Clock Controller) 링크 클릭하면 아래와 같이 나타난다. 빨간 화살표를 클릭해 가면서 Clock Configuration 패널의 변화를 살펴보자.

보드를 개발할 시에는 내부 클럭 발생기의 오차가 심해서 외부 클럭발진기를 사용해야 한다. 

HCLK(MHz)를 100으로 바꾸어 준다. 그러면 Main PLL 값과 기타 파레메타 값이 자동 조정된다.

코드생성시 main.c에 함수 실행문만을 넣고 보조장치(GPIO, USART 등등)에 관련된 파일은 따로 모아서 .c 파일로 만들어 주려면 아래 체크 박스를 체크해준다.

그 이후에 코드를 만들어 준다.

아래의 메뉴가 뜨는데… 

No를 누르면 C/C++ 파일을 만들어 주고 끝난다. Yes를 누르면 ‘work bench’와 perspective를 만들어서 보여준다.

work bench : 일할 때 걸터 앉는 의자. 작업화면이라고 하겠다.

perspective : 원근법이 사전적 의미이지만, 대상물(예를 들면 main.c)을 바라보기 위해서 필요한 영역, 조그만 창, 프레임을 말한다. 

위와 같은 화면이 뜨면 main.c를 읽어보며, 프로젝트 익스플로러에서 어떠한 파일이 생성되었는지를 확인해 본다.

(위 그림은 “Generate peripheral … “의 체크박스를 비어 놓았을 때의 화면이다.)

/* USER CODE BEGIN ~~~ */ 

여기에 코드를 넣어준다.

/* USER CODE END ~~~   */ 

main.c에는 다음과 같은 명령어가 있다.

HAL_Init(); 할 드라이버, 헬드라이버 초기화

SystemClock_Config(); 시스템클럭 초기화

MX_GPIO_Init(); 내 I/O 포트 초기화
MX_USART2_UART_Init(); USART 포트 초기화

/* USER CODE BEGIN WHILE */ 

while (1) 

여기에 나의 명령어를 넣는다.

/* USER CODE END WHILE */ 

명령어를 일일이 인터넷에서 찾아 넣을 수는 없고 앞 몇글자 넣으면 다음 글자들 선택창이 뜨게 하려면 Ctrl+Space를 넣으면 된다. 단, 키보드3를 사용하면 이 단축키를 Ctrl+Enter로 바꾸면 편하다.

명령어 위에서 F3키를 누르면 이 명령어가 정의된 곳으로 이동해서 펑션을 보여준다.

예1. GPIO에 연결된 스위치를를 누르면 LED가 불 들어 오게 하는 코드 생성

위 메뉴에 들어가면, PA5가 Green LED에 연결, B1 Blue 스위치가 PC13에 연결되어 있음을 알 수 있다. 전원이 인가되면 LED 불이 1초마다 점등, 파란스위치를 누르고 있으면, LED 불이 계속 들어온다.

if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==GPIO_PIN_SET) {
	HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
    HAL_Delay(1000);
    HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
    HAL_Delay(1000);
}
else {
	HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
}

예 2. 시리얼 포트 통신

– 1초마다 문자 ‘a’를 전송하는 프로그램

  uint8_t pData='a'; 
  while (1) { 
  	HAL_UART_Transmit(&huart2, &pData, 1, 10); 
  	HAL_Delay(1000);
  }


– 전송된 값을 다시 전송해주는 프로그램

unit8_t pData;
while (1) { 
   if (HAL_UART_Receive(&huart2, &pData, 1, 10)==HAL_OK) { 
   HAL_UART_Transmit(&huart2, &pData, 1, 10); 
   HAL_Delay(1000); 
}

– 문자열, 정수 및 실수 값을 전송하는 프로그램

/* Includes ------------------------------------------------------------------*/ 
#include "main.h" 
#include "usart.h" 
#include "gpio.h" 

/* Private includes ----------------------------------------------------------*/ 

/* USER CODE BEGIN Includes */ 
#include <stdio.h> 
/* USER CODE END Includes */

/* USER CODE BEGIN PFP */ 
int _write(int file, char* p, int len) { 
	HAL_UART_Transmit(&huart2, p, len, 10); 
	return len; 
} 
/* USER CODE END PFP */

uint8_t a=0; 
float f=1.234; 

while (1) { 
	printf("Hello %d %f\n", a++, f); 
	HAL_Delay(1000);
}

정수값만 넣을 때 warining이 나타난다. 이유는 float 데이타 형을 printf(로 내보낼 수 가 없기 때문이다. 아래 화면과 같이 Properties를 연 후, 

아래 그림과 같이 Other flags 창에 “-u _printf_float”를 입력한 후 “Apply and Close”한다.

그러면 그 결과는 다음과 같이 나타난다. 위 그림과 같이 안하면 컴파일 시 waring msg가 나오며 소숫점이 출력되지 않는다.

예 3. RX 인터럽트

이벤트의 처리에는 항상 감시상태에 있으면서 수신값을 기다리는 ‘폴링’ 방식과 다른 짓을 하다가도 ‘인터럽트’가 발생하면 수신값이 발생했음을 알고 이에 대처하는 루틴을 실행하는 인터럽트 방식이 있다.Poll은 머리이다. 투표는 머릿수 대결.  선거한다는 것은 투표한다는 것과 같은 의미이다. 지평선 너머에 사람의 poll이 나타나는지 감시하고 있는 것, polling 방식이다. 휴전선 GOP에 감지센서를 설치한 후 센서신호가 발생하면 신호를 발생시키는 방식이 인터럽트 방식.

아래 그림 Conncetivity 메뉴의 USART2 패널의 USART Global Interrupt 값을 Enable로 변경해 준다.

참조: NVIC(Nested Vectored Interrupt Controller)

그리고 System Core 메뉴에서 NVIC 설정을 다음과 같이 “USART2 global interrupt”의 “Select for init sequence ordering”에 체크한다.

generate code하면, 인터럽트 관련 소스파일들이 주가된다.

아래 코드에서 /*USER CODE BEGIN 4*/ 안에 내용이 인터럽트가 발생하면 그 값을 그대로 다시 전송하는 함수(콜백함수)를 구현한 것이다.

콜백함수 : 이벤트가 발생하면 이를 처리하기 위한 함수.

/* USER CODE BEGIN PV */
/* Private variables ----------------------------------------------------------*/ 
uint8_t rx2_data;
/* USER CODE END PV */

/* USER CODE BEGIN 2 */ 
uint8_t pData='a'; 
uint8_t a=0; 
float f=1.234; 
HAL_UART_Receive_IT(&huart2, &rx2_data, 1); 
/* USER CODE END 2 */

static void MX_NVIC_Init(void) { 
	/* USART2_IRQn interrupt configuration */ 
  	HAL_NVIC_SetPriority(USART2_IRQn, 0, 0); 
  	HAL_NVIC_EnableIRQ(USART2_IRQn); 
} 

/* USER CODE BEGIN 4 */ 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { 
	if(huart->Instance==USART2) { 
  		HAL_UART_Receive_IT(&huart2,  &rx2_data, 1); 
  		HAL_UART_Transmit_IT(&huart2,  &rx2_data,  1, 10); 
  	}
} 
/* USER CODE END 4 */

예 4. 클리스탈 LCD

사용 핀 설정을 완료한다.

코드 업데이트한다.

CLCD PINPin NameMCU or Parts
1VSSGND
2VCC5V
3V0가변저항
4RS(Register Select)PC7
5R/WGND
6EPC6
7~10D0~D3Not Connected
11D4PB15
12D5PB14
13D6PB13
14D7PB12
15LED+220오옴 저항.
저항은  VCC와 연결.
16LED-GND

굵게 표시된 핀 네임은 꼭 변경해 놓아야 한다.

위 표와 그림을 이용해서 배선을 완료한다.

아래 2개의 파일을 프로젝트에 넣는다. 출처 : www.youtube.com/watch?v=zfn5YqFIqbc&feature=youtu.be

STM_MY_LCD16X2.hSTM_MY_LCD16X2.h0.00MB

STM_MY_LCD16X2.cSTM_MY_LCD16X2.c0.01MB

/* USER CODE BEGIN 2 */ 
  LCD1602_Begin4BIT(RS_GPIO_Port, RS_Pin, E_Pin, D4_GPIO_Port, D4_Pin, D5_Pin, D6_Pin, D7_Pin); 
/* USER CODE END 2 */

/* Infinite loop */ 
/* USER CODE BEGIN WHILE */ 
while (1) { 
	LCD1602_print("HELLO WORLD"); 
  	HAL_Delay(1000);
  	LCD1602_clear(); 
  	LCD1602_print("GOODBY WORLD"); 
  	HAL_Delay(1000); 
  	LCD1602_clear(); 
/* USER CODE END WHILE */ 

/* USER CODE BEGIN 3 */ 
} 
/* USER CODE END 3 */ 

LCD 예제에서 라이브러리 파일을 찾고 인스톨한 후 이 라이브러리 파일을 이용하는 방법을 연습해 보았다.

예제 5. 타이머

One Pulse Mode는 비워놓아야 한다.

Prescale은 2^16 -1까지 값을 가짐. 클럭 주파수를 Prescale 값으로 묶는다.  이렇케 묶으면 Counter Period(AutoReload Register) 은 이러한 묶음의 갯수를 의미한다. HSCExternal 100Mhz 속도로 셋팅되어 있다면, [10^2 * 10^6 / 10^4 ] / 10^4 => 1sec. 인터럽트를 1초마다 두기 위한 설정 값이다. 0부터 시작하기 때문에 9999 값을 가진다.

while 문 위가 다음과 같은지 확인/변경.

HAL_UART_Receive_IT(&huart2, &rx2_data, 1);
HAL_TIM_Base_Start_IT(&htim10); // 타이머 인터럽트가 실행되도록 하는 명령어

LCD1602_Begin4BIT(RS_GPIO_Port, RS_Pin, E_Pin, D4_GPIO_Port, D4_Pin, D5_Pin, D6_Pin, D7_Pin);
  /* USER CODE END 2 */

/*USER CODE BEGIN 4 */ 가 위치하는 부분의 코드가 아래와 같이 변경한다.

static void MX_NVIC_Init(void) { 
  	/* USART2_IRQn interrupt configuration */ 
  	HAL_NVIC_SetPriority(USART2_IRQn, 0, 0); 
  	HAL_NVIC_EnableIRQ(USART2_IRQn); 
  	/* TIM1_UP_TIM10_IRQn interrupt configuration */ 
  	HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 0, 0); 
  	HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn); 
} 

/* USER CODE BEGIN 4 */ 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { 
	if(huart->Instance==USART2) { 
		HAL_UART_Receive_IT(&huart2, &rx2_data, 1); 
		HAL_UART_Transmit_IT(&huart2, &rx2_data, 1); 
  	} 
} 

void HAL_TIM_PeriodElapsedCallback(Tim_HandleTypeDef *htim) { 
	if(htim->Instance == TIM10) { 
		HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); 
  	} 
} 
/* USER CODE END 4 */


1초마다 깜박거리는 LED를 볼 수 있다.

예 6. 외부인터럽트

외부인터럽트를 이용해서 파란색 버튼이 연결된 포트를 제어하기 위해서 포트설정을 바꾸었다.

외부인터럽트를 이용할 때에는 포트번호가 중요하다. 같은 포트는 동일한 인터럽트를 발생한다. PA0포트와 PB0 포트는 동일한 인터럽트를 발생한다고 보면 된다.

GPIO는 포트 0부터 15까지 있는데 0부터 4까지는 각각의 인터럽트가 다르다. 5개가 있다고 보자. 그런데 포트 6 부터 9까지 그리고 10부터 15까지는 각각 EXTI9_5와 EXTI15_10를 발생시킨다. 인터럽트를 처리를 빠르게 하려면 0부터 4까지 사용하면 될 것 같다.

HAL_UART_Receive_IT(&huart2, &rx2_data, 1);
//HAL_TIM_Base_Start_IT(&htim10); // 타이머 인터럽트가 실행되도록 하는 명령어
HAL_GPIO_EXTI_Callback(BP_Pin);

LCD1602_Begin4BIT(RS_GPIO_Port, RS_Pin, E_Pin, D4_GPIO_Port, D4_Pin, D5_Pin, D6_Pin, D7_Pin);
  /* USER CODE END 2 */

while() {
...
}
...
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { 
	if(GPIO_Pin == BP_Pin) { 
		HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); // LED를 토글한다.
	} 
}
/* USER CODE END 4 */

버튼을 누르면 LED2의 불이 토글된다.

예) 타이머를 이용해서 PWM의 펄스폭을 조절한다.

CC Register(Capture Compare Register) 값에서 위상이 바뀐다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다