UART TX에 이어 컴퓨터에서 MCU로 데이터를 보내는 UART RX코드를 작성할 것이다.

 

 

UART 코드 생성 (0)

NVIC
DMA

UART 코드 생성에서 기존 파라미터는 변화가 없고 NVIC 세팅과 DMA 세팅만 바꿔주고 코드를 생성한다.

 

global interrupt에서 IDLE 인터럽트와 RXNE 인터럽트를 사용할 생각이다.

 

RX DMA를 사용할 때 RXNE인터럽트는 사용하지 않고 IDLE인터럽트만 사용하여 코드를 작성해 볼 것이다.

 

DMA는 FIFO를 사용하지 않고 Normal 모드를 사용한다.

 

 

코드 - RX (1)

UART RX의 기본적인 흐름이다.

 

TX와 다르게 RX의 경우 4번의 과정만 제외한 1번에서 6번까지 과정이 전부 생성되어 있다.

 

따로 인터럽트 비트만 설정해주면 인터럽트를 실행할 수 있다.

 

인터럽트를 사용하므로 main.c와 stm32f4xx_it.c 두 개의 파일에서 코드를 작성할 것이다.

 

main.c

1
2
3
uint16_t buf_counter=0;
uint8_t uart_buf[20]={0};
uint8_t receive_condition=0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  /* USER CODE BEGIN 2 */
  LL_USART_EnableIT_RXNE(USART1);
  LL_USART_EnableIT_IDLE(USART1);
  printf("start\r\n");
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
      if(receive_condition){
          uart_buf[buf_counter]=0x00;
          receive_condition=0;
          printf("str%d:%s\n\n\r",buf_counter,uart_buf);
 
          buf_counter=0;
      }
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

 

main.c 파일에 global 변수 buf_counter, uart_buf[20], receive_condition를 선언해준다.

 

buf_counter는 uart_buf에 채워 넣을 순서를 나타낸다.

 

uart_buf는 실제 uart로 받은 변수들을 저장하는 배열이다.

 

receive_condition은 IDLE 인터럽트가 발생하면 SET 되어 데이저 수신이 완료됐다고 나타내는 변수이다.

 

3개의 변수들은 stm32f4xx_it.c 파일에서도 사용된다.

 

LL_USART_EnableIT_RXNE()와 LL_USART_EnableIT_IDLE() 으로 RXNE 인터럽트와 IDLE 인터럽트를 ENABLE 한다.

 

while문 내부에서 recevie_condition이 SET 될 경우 uart_buf의 마지막 부분에 0x00을 넣어 문자열을 끝을 알린다.

 

문자열을 printf로 출력하고 버퍼에 데이터가 얼마나 들어왔는지도 출력한다.

 

stm32fxx_it.c

1
2
3
extern uint16_t buf_counter;
extern uint8_t uart_buf[20];
extern uint8_t receive_condition;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
    if(LL_USART_IsActiveFlag_RXNE(USART1)){
        LL_USART_ClearFlag_RXNE(USART1);
        uart_buf[buf_counter]=LL_USART_ReceiveData8(USART1);
        buf_counter++;
    }
    if(LL_USART_IsActiveFlag_IDLE(USART1)){
        LL_USART_ClearFlag_IDLE(USART1);
        receive_condition=1;
    }
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */
 
  /* USER CODE END USART1_IRQn 1 */
}
 
 

 

extern 키워드로 main.c에 선언되어있는 3개의 변수들을 사용할 수 있게 한다.

 

USART1 인터럽트 서비스 루틴 코드는 RXNE flag가 SET 되었을 경우, IDLE flag가 SET 되었을 경우 두 가지 인터럽트가 발생하면 실행된다.

 

RXNE 인터럽트는 데이터를 한 개 받았을 때 발생하여 데이터를 uart_buf에 집어넣는 코드이다.

 

IDLE 인터럽트는 데이터 전송이 끝나면 발생되는 인터럽트이고 receive_condition을 SET 하여 main.c의 while문에서  uart_buf 내용을 출력한다.

 

결과

실제 MCU에 코드를 올리고 터미널 프로그램으로 데이터를 보내보면 터미널로 보낸 데이터들을 출력하는 모습을 볼 수 있다.

 

 

코드 - RX DMA (2)

DMA에 대해 자세히 쓰기에는 양도 많고 어렵기 때문에 그 부분은 넘어가고 DMA로 데이터를 받을 때 필요한 코드 위주로 쓰도록 하겠다.

 

길이가 일정하지 않은 데이터를 받는 상황을 가정하고 코드를 작성하고 있기 때문에 DMA 인터럽트인 TC인터럽트, HT 인터럽트를 사용하지 않고 UART IDLE 인터럽트만 사용했다.

 

main.c

global 변수 buf_counter, uart_buf[20], receive_condition는 위의 코드와 똑같이 선언해둔다.

 

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
  /* USER CODE BEGIN 2 */
  LL_DMA_SetMemoryAddress(DMA2,LL_DMA_STREAM_2,(uint32_t)uart_buf);
  LL_DMA_SetPeriphAddress(DMA2,LL_DMA_STREAM_2,(uint32_t)(&USART1->DR));
  LL_DMA_SetDataLength(DMA2,LL_DMA_STREAM_2,20);
  LL_DMA_EnableStream(DMA2,LL_DMA_STREAM_2);
 
  LL_USART_EnableDMAReq_RX(USART1);
  LL_USART_EnableIT_IDLE(USART1);
 
  printf("start\r\n");
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
      if(receive_condition){
 
          buf_counter=(uint8_t)(20-LL_DMA_GetDataLength(DMA2,LL_DMA_STREAM_2));
          uart_buf[buf_counter]=0x00;
 
          printf("str%d:%s\n\n\r",buf_counter,uart_buf);
          LL_DMA_DisableStream(DMA2,LL_DMA_STREAM_2);
 
          //Before setting EN bit to '1' to start a new transfer, the event flags corresponding to the
          //stream in DMA_LISR or DMA_HISR register must be cleared.
          LL_DMA_ClearFlag_TE2(DMA2);
          LL_DMA_ClearFlag_HT2(DMA2);
          LL_DMA_ClearFlag_TC2(DMA2);
          LL_DMA_ClearFlag_DME2(DMA2);
          LL_DMA_ClearFlag_FE2(DMA2);
          //clear DMA_LISR or DMA_HISR register
 
          LL_DMA_EnableStream(DMA2,LL_DMA_STREAM_2);
 
          receive_condition=0;
          buf_counter=0;
      }
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
 

 

LL_DMA_SetMemoryAddress() 함수로 데이터가 저장될 주소를 레지스터에 설정한다. uart_buf 시작 주소를 설정했다.

 

LL_DMA_SetPeriphAddress() 함수로 데이터를 받을 주소를 레지스터에 설정한다. USART1 DR 레지스터의 주소를 설정했다.

 

LL_DMA_SetDataLength() 함수로 DMA가 전송할 데이터의 길이를 설정할 수 있다. 이때 DMA가 enable 상태일 경우 값을 변경할 수 없다.

 

LL_DMA_EnableStream() 함수로 DMA를 enable 상태로 SET 한다.

 

LL_USART_EnableDMAReq_RX() 함수로 DMA request를 enable 한다. 설정하지 않을 경우 DMA에 request를 보내지 않아 데이터를 받을 수 없다.

 

while 문 내부의 LL_DMA_Get_DataLength()로 SxNDTR레지스터의 값을 읽어와 전송할 수 있는 데이터의 양을 알 수 있다.

 

LL_DMA_DisableStream()과 LL_DMA_EnableStream() 사이의 코드들이 중요하다고 생각하는데, 우선 DMA를 disable 하고 다시 enable 하기 위해서는 모든 flag들을 RESET 해야 한다.

또한 남은 데이터양을 나타내는 레지스터 SxNDTR의 레지스터를 처음 설정한 값으로 초기화하려면 DMA를 disable 하고 다시 enable 하거나 circular 모드로 설정해야 한다.

코드에서는 SxNDTR레지스터의 값을 다시 20으로 세팅하기 위해 DMA를 disable 하고 다시 enable 해준 것이다.

 

stm32f4xx_it.c

 

1
2
3
4
5
6
7
8
9
10
11
12
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
    if(LL_USART_IsActiveFlag_IDLE(USART1)){
        LL_USART_ClearFlag_IDLE(USART1);
        receive_condition=1;
    }
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */
 
  /* USER CODE END USART1_IRQn 1 */
}
 

 

USART1 인터럽트 서비스 루틴 코드는 수신이 끝난 것을 알려주기 위한 IDLE 인터럽트에 관한 코드만 작성했다.

 

결과

MCU에 코드를 올리고 실행한 결과이다.

 

RX 인터럽트를 사용한 결과와 다르지 않지만 버퍼에 데이터를 넣을 때 CPU에서 명령을 내리지 않고 DMA가 메모리에 결과값을 넣었다는 점이 다르다.

 

 

 

-

UART TX에 이어 RX에 관련된 글을 작성했다.

 

RX는 GPS 데이터를 받거나 컴퓨터로 드론의 파라미터 값을 변경하거나 리시버의 시리얼 데이터를 받을 때 중요하게 사용된다.

 

DMA와 Peripheral에 대해 둘 다 알고 있어야 해서 어려울 수 있는 내용이다.

 

다음 글은 i2c에 관한 글을 쓸 생각이다.

Posted by DDTXRX
,