STM32 UART통신 #3

김상인·2025년 5월 10일

이전에 echo(키보드 입력값을 출력으로 보여주는)를 UART통신으로 구현했는데, 책에서 이번 파트가 UART로 echo를 구현하는 내용이였다. 뒤에 이런 내용이 있는 줄 모르고 '중복이네,,'싶었다가 버퍼와 인터럽트에 관한 내용이 있어서 실습을 진행했다.

Hello, Wolrd!를 출력하거나 키보드 입력대로 출력되게한 이전 실습은 Polling방식으로 송수신을 하였다. 그러나 Polling방식으로 송수신할 때 이보다 더 복잡한 프로그래밍을 짜게 된다면, 데이터를 수신할 때 제대로된 데이터를 받지 못할 확률이 높을 것이다. 그래서 이번 파트에서는 UART 수신 인터럽트와 링 버퍼를 활용한 Echo실습을 진행한다.

역시 프로젝트 생성과 외부 클럭 비활성화를 똑같이 진행한다. 그 다음 USART2 탭에서 Parameter Settings를 통해 셋팅된 값들을 확인한다. NVIC Settings에서 인터럽트를 활성화해준다.

HAL_UART_IRQHandler()함수 호출 코드 생성을 막기 위해 NVIC 카테고리를 선택하고 Code generation탭을 선택해 USART2 global interrupt의 Call HAL handler 체크를 해제한다.

클럭 설정을 확인하면 최적의 상태로 클럭 설정이 되어있는 것을 확인할 수 있다.
그 후에 코드를 생성해준다. stm32f1xx_it.c에서 USART2_IRQHandler() 함수를 확인해보자.
아까 체크를 해제해 호출함수가 비어있는 것을 볼 수 있다.

이제 책에서 소개하는 링 버퍼 코드를 저자의 네이버카페에 가입해서 다운받는다. 차라리 깃허브에 올렸으면 덜 불편했을텐데ㅡㅠ 아무튼 시키는대로 헤더파일과 코드파일을 각 폴더에 맞게 넣어주고, 해당 프로젝트를 우클릭 후 Refresh 시킨다.

그리고 아까 비어있는 것을 확인한 USART2_IRQHandler()함수에 아래와 같이 코드를 작성한다. RXNE는 Receive Not Empty라는 뜻으로, 데이터 수신을 했고 UART RXNE Interrupt가 활성화 되어있으면 조건문 내의 코드가 실행된다. RXNE 인터럽트 source bit가 활성화는 나중에 코드로 활성화시킨다.

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
	if((__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) != RESET) && (__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_RXNE) != RESET)) {
		HAL_UART_RxCpltCallback(&huart2);
		__HAL_UART_CLEAR_PEFLAG(&huart2);
	}
  /* USER CODE END USART2_IRQn 0 */
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}

main.c에서 파일추가한 rb.h 헤더를 포함시키고 링 버퍼 변수를 정의한다.
HAL_UART_RxCpltCallback() 함수도 추가해 구현한다.
인터럽트가 USART2에서 발생했으면 데이터 레지스터와 비트연산으로 마스킹 작업을 한다. 하위 8비트에 데이터가 담긴다고 한다. 그리고 이 값을 링 버퍼에 쓰기작업을 한다.

...
/* USER CODE BEGIN Includes */
#include "rb.h"
/* USER CODE END Includes */
...
/* USER CODE BEGIN PV */
RingFifo_t gtUart2Fifo;
/* USER CODE END PV */
...
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle) {
	uint8_t rx;

	if(UartHandle->Instance == USART2) {
		rx = (uint8_t) (UartHandle->Instance->DR & (uint8_t) 0x00FF);
		RB_write(&gtUart2Fifo, rx);
	}
}
/* USER CODE END 4 */
...

다음 작업으로는 아까 말했던 RXNE 인터럽트를 활성화시키고 링 버퍼를 초기화시킨다. 링 버퍼의 사이즈는 2의 n제곱 단위로 지정해줘야한다.

  /* USER CODE BEGIN 2 */
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);
  if(RB_init(&gtUart2Fifo, 16)) {

  }
  /* USER CODE END 2 */

그리고 링 버퍼에 비어있는지 확인하고, 비어있지 않으면 링 버퍼의 데이터를 읽어 ch변수에 저장한다. 이 저장한 ch값을 UART통신으로 송신한다.

  uint8_t ch;

  while (1)
  {
	  if(!RB_isempty(&gtUart2Fifo)) {
		  ch = RB_read(&gtUart2Fifo);
		  HAL_UART_Transmit(&huart2, &ch, 1, 0xFF);
	  }
    /* USER CODE END WHILE */

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

실행 후 결과를 확인하는데 이번에 시리얼 통신 프로그램을 그냥 설치했다. 이유는 여기서 문자열을 한번에 전송하는 실험이 필요한데, iterm2 screen명령어 내에서는 해당 작업이 안되는 듯하다. 그리고 무엇보다 명령어식으로 처리하는 것보다 마우스로 몇번 딸깍하면 연결되는 편리성에서도 길게보면 이게 훨씬 낫다.
CoolTerm이라는 프로그램을 사용했는데 아래와 같이 문자열을 한번에 보낼 수 있다.

데이터 누락없이 잘 출력되는 것을 확인할 수 있다. 여기서 버퍼 사이즈를 2로 하고 똑같은 문자열을 보내본다면?

숫자에서 알파벳으로 전환되는 구간에 0이나 a가 누락되는 것을 확인할 수 있다.

profile
이것저것 해보기

0개의 댓글