본문 바로가기
임베디드 쌩초보 공부/STM32

STM32 CubeIDE , HAL을 사용한 Uart/Usart 공부(1) Rx Interrupt

by 존버매니아.임베디드 개발자 2021. 12. 12.
반응형

★ CubeIDE의 Code Gen 기능과 HAL 라이브러리를 사용하여 Uart 메세지를 인터럽트 형태로 받아보자

 

참고로 Usart의 s는 Synchronous 를 의미한다.

우리가 사용하는 MCU에서 UART 1,2,3은 Synchronous가 지원되는데 UART4는 지원 안되서 Async만 된다.

그래서 Cube에서 셋팅할때 보면 1,2,3은 USART 라고 적혀있는데 4는 UART라고 적혀있다.

Sync, Async 에 대한 상세한 차이는 시간되면 공부해보자. 지금은 모르겠다.


Cube IDE 로 GUI를 사용하여 Uart와 관련된 셋팅을 해주자.

Cube 에서 사용할 핀에 대한 셋팅을 하면되는데

일단 우리 예시에서는 UART4 를 사용해보자. GUI 툴로 셋팅을 해준다.

 

1) 파라미터 셋팅

BaudRate , Word Length, Parity, Stop Bits 등등 이런저런 셋팅을 해주자.

셋팅의 상세의미는 나중에 알아보자.

(근데 Cube IDE에서 셋팅 하는 값들에 대한 정보 나타내주는 매뉴얼 없나??)

 

2) NVIC Settings

에서 인터럽트 Enable 하자.

Preemption Priority랑 Sub Priority 개념이 뭐지??

그리고 왜 우선순위 여기서 셋팅이 안되지? 원래 고정인건가 아니면 나중에 코드에서 바꿀수있는건가??

 

3) GPIO Settings

통신에 사용할 포트도 골라주자.

근데 이 보드에 uart4에서 쓸수있는 핀은 PA0, PA1 2개 뿐이라 따로 고를건 없다.

 

이렇게 셋팅하고 Code Gen을 한다.


Code Gen을 하자. ( Code gen 결과를 분석해보자. 그냥 사용만 할거면 분석할 필요는 없음)

1) main.c 에는 MX_UART4_Init 함수가 생성되고 (여기에서 baudrate,wordlength,parity,hwflowctrl 등등을 셋팅)

2) stm32l4xx_hal_msp.c 에는 HAL_UART_MspInit 함수가 생성되고 (여기에서 인터럽트 enable, 인터럽트 우선순위 셋팅 , rx tx 핀 셋팅 , 클락관련 셋팅)

3) stem32l4xx_it.c UART4_IRQHandler 함수가 생성된다.

     ※  code gen과 관련된 상세한 내용은 다른 포스팅 참고

2021.12.12 - [임베디드 쌩초보 공부/STM32] - STM32 CubeMx Code Generation 상세분석

 

이 상태 그대로 빌드해도 기본적으로 mcu가 uart 통신 할 준비는 끝났다.

그러면 이제는 실제로 메세지를 어떻게 받는지 알아보자.


HAL_UART_Receive_IT 함수 , 그리고 HAL_UART_RxCpltCallback 함수에 대하여 

인터럽트를 사용하여 메세지를 수신 받기 위해서 우리는 Uart Hal 라이브러리 중에 아래의 HAL_UART_Receive_IT 함수를 쓸 것이다.

참고로 STM32 와 관련된 코드나 함수중에 끝에 _IT 가 붙으면 이것은 뭔가 인터럽트랑 연관된거라고 생각하면 된다.

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

위 함수의 첫번째 파라미터에는 &haurt4 가 들어가면 되는데,

UART_HandleTypeDef huart4;

cube mx에서 uart4를 사용한다고 체크 할 경우 main.c 에 위와 같은 변수 하나가 자동 생성된다.

huart4는 uart채널 4번에 대한 configure 값을 갖고있는 구조체 변수이고,

HAL_UART_Recevei_IT 함수를 호출 할 때 첫번째 파라미터에 넣으면된다.

만약 우리가 Uart 1번을 사용한다면 huart4가 아니라 &huart1을 쓰면 될 것이다.

 

다음으로 pData 에는 uart로 부터 수신받은 데이터를 담을 배열을 써주면 된다.

 

마지막으로 size가 있는데 이 size는 callback 함수인 HAL_UART_RxCpltCallback 의 동작과 연관이 있는데

uart의 RX ISR(인터럽트 서비스루틴) 인 UART4_IRQHandler는 사실 이 size 변수 값이 얼마인지랑 무관하게 1byte 데이터를 수신하면 UART4_IRQHandler가 호출되고 

우리가 지정한 Rx buffer에 1byte씩 수신데이터 값이 쓰여진다. 그렇게 값이 쓰여지다가 해당 사이즈만큼의 바이트를 수신 받으면 그때 호출되는 함수가 바로 HAL_UART_RxCpltCallback 함수이다.

참고로, HAL_UART_RxCpltCallback 함수가 호출되기전에 uart Rx Enable bit가 clear 되기 때문에 더이상 Rx 인터럽트가 발생되지 않는다.

 

 

동작을 다시 한번 설명해 보면

예를 들어 uart와 관련된 init 함수들을 모두 실행 한 후,

HAL_UART_Receive_IT(&huart4, &rxbuffer[0], 5);

위와 같은 함수를 호출하면, 

그 때부터 유아트로 메세지가 mcu에게 날아오면 1byte 날아올때마다 IRQ Handler가 실행되고 rxbuffer에는 데이터가 1byte씩 차례차례 담기게 된다. 

그러다가 5byte까지 데이터가 수신되면 Rx 인터럽트 bit가 disable 된 후 HAL_UART_RxCpltCallback함수가 호출이 된다.

이제 Rx 인터럽트 bit가 disable 됐으므로, 앞으로는 mcu에게 유아트 메세지가 날아와도 인터럽트도 발생되지않고 Rx buffer에 데이터가 담기지 않는다.

 

그러므로, 5바이트 수신후에 또 메세지를 받고싶다면?

HAL_UART_Receive_IT(&haurt4, &rxbuffer[0], 5); 함수를 다시호출 해야한다.

 

그래서 보통 어떤식으로 사용하냐면, 일단 최초로 메세지를 받기 위해서 한번은 호출해야되니까

main 함수에서 HAL_UART_Receive_IT(&haurt4, &rxbuffer[0], 5); 를 한번 호출하고,

그러고 나서 5byte 수신받고나면 HAL_UART_RxCpltCallback이 실행되니까 

이 callback 안에서 HAL_UART_Receive_IT(&haurt4, &rxbuffer[0], 5);  를 호출하는 식으로 보통 코드를 구성한다.

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){

  if (huart->Instance == USART4)
  {
      HAL_UART_Receive_IT(&huart4, &rxbuffer[0], 5); 
  }
        
}

참고로 HAL_UART_RxCpltCallback 함수는 weak 형태로 HAL 라이브러리에 정의되어있으므로

위와 같이 우리가 재정의해서 쓰면된다.

 

이렇게되면 rx 인터럽트를 통해서 메세지가 계속수신될거고 5byte 수신받을때마다 callback 함수가 실행되는 형태로 코드가 수행될 것이다.

 

참고로, 여러개의 uart 채널에서 rx 인터럽트를 사용할 경우, 위 콜백이 실행됏을때 몇번 채널에 의해서 실행된 것인지 구분해야 하므로, 위 코드에 그것을 확인하는 코드가 if문 형태로 들어가있다.


HAL_UART_Receive_IT 함수 , 그리고 HAL_UART_RxCpltCallback 동작 상세하게 분석해보기

몰라도 사용하는데는 지장없지만 그냥 내부 분석해보자.

 

HAL_UART_Receive_IT 함수의 전체적인 실행 흐름을 설명한다.

 

우리가 code gen을 하면 각종 init 함수들이 자동생성되서 통신 할 준비가 완료된다고 하였다.

또한 HAL_UART_MspInit 함수 안에서는 아래와 같이 인터럽트에 대해서도 enble을 시켜준다.

/* UART4 interrupt Init */
    HAL_NVIC_SetPriority(UART4_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(UART4_IRQn);

자, 그러면 우리가 code gen만 해놓은 상태로 코드를 빌드해서 SW를 다운로드 한 후,

Uart 핀을 통해서 메세지를 MCU에게 날려주면 Uart RX Irq 가 실행이 될까?

Uart 통신과 관련된 각종 Init 작업을 해주었고, 인터럽트 우선 순위 셋팅, 인터럽트 Enable 까지 해주었으니

인터럽트가 발생한다고 생각할 수 있다.

 

그러나 정답은 IRQ는 발생하지 않는다.

왜냐면 인터럽트를 enable 시키는 것은 몇 가지 단계로 구분되어있다.

위 코드에서 HAL_NVIC_EnableIRQ 함수만 호출했다고해서 Uart Rx 관련 인터럽트가 실행되는게 아니라

메세지 수신 인터럽트를 발생시키려면 메세지 수신과 관련된 인터럽트 enable 플래그를 별도로 set을 해주어야 한다.

 

그러면 그 enable flag는 어떻게 set 하는걸까?

 

정답은 바로 앞에서 살펴본 HAL_UART_Receve_IT 함수 안에서 한다. 

 

그러므로 RX 메세지를 인터럽트 형태로 수신받기 위해서는 일단 HAL_UART_Receve_IT 를 호출해야한다.

HAL_UART_Receve_IT  함수 안에서는 인터럽트 bit를 enable 을 하고 또 아래와 같이

RxISR 함수를 UART_RxISR_8BIT_FIFOEN 이라는 함수로 지정해준다. 그래서 이게 어떻게 동작하냐면..

자 이제 최초로 HAL_UART_Receve_IT  를 호출해주면? Rx 관련 인터럽트가 enable 되고

따라서 1byte 메세지를 수신받으면 IRQ인 UART4_IRQHandler가 실행될 것이고 UART4_IRQHandler는

void UART4_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart4);
}

HAL_UART_IRQHandler 함수를 호출한다. 이 함수 내부 동작을 살펴보면

아까 위에서 RxISR로 지정해줬던 UART_RxISR_8BIT_FIFOEN 함수를 호출한다.

 

그래서 UART_RxISR_8BIT_FIFOEN  함수 내부를 살펴보면 

uart로 수신받은 데이터를 HAL_UART_Receve_IT 호출할 때 지정한 rx buffer에 값을 옮겨적은 후,

HAL_UART_Receve_IT 함수를 호출할때 size를 지정하는데 , 이 size 값을 1 감소시킨다.

 

이 때 사이즈가 0이면 rx 인터럽트 bit를 disable 하고 callback 함수를 호출한다.

1감소시켰는데 사이즈가 0이 아니면? 원래 작업하던걸 그대로 하게한다.

 

 

 

그래서 위 코드를 다시 분석해보면..

HAL_UART_Receive_IT(&haurt4, &rxbuffer[0], 5); 

이 함수를 호출하면 Rx 인터럽트가 enable 되서 메세지 1바이트 수신될때마다 Rx IRQ가 실행되고

Rx IRQ가 실행될 때마다 UART_RxISR_8BIT_FIFOEN  함수가 호출되서 수신된 데이터가 Rx buffer로 옮겨지고 1바이트 옮겼으니까 사이즈를 4로 감소 시킴.

 

그러고 메세지가 수신되면 또 위와같이 동작해서 Rx buffer에 데이터 옮기고 사이즈를 3으로 감소..

그러고 메세지가 수신되면 또 위와같이 동작해서 Rx buffer에 데이터 옮기고 사이즈를 2으로 감소..

그러고 메세지가 수신되면 또 위와같이 동작해서 Rx buffer에 데이터 옮기고 사이즈를 1으로 감소..

그러고 메세지가 수신되면 또 위와같이 동작해서 Rx buffer에 데이터 옮기고 사이즈를 0으로 감소..

이제 사이즈가 0이 됐으니까 인터럽트를 disable 하고 callback을 호출

 

 

내부가 이런식으로 동작한다.

결론적으로.. 메세지 5바이트 수신할때마다 rx 인터럽트가 disable되고 callback이 뜬다는 거다.

반응형