지난 번의 IDLE IT 를 사용한 DMA 테스트는 UART2에서 한 테스트였고,

이번에는 UART1 에서 테스트를 해 봤다.

수정할 내용만 적어 보면 다음과 같다.

UART1 인터럽트함수의 코드는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
    /* Check for IDLE flag */
    if (USART1->SR & UART_FLAG_IDLE) {         /* We want IDLE flag only */
        /* This part is important */
        /* Clear IDLE flag by reading status register first */
        /* And follow by reading data register */
        volatile uint32_t tmp;                  /* Must be volatile to prevent optimizations */
        tmp = USART1->SR;                       /* Read status register */
        tmp = USART1->DR;                       /* Read data register */
        (void)tmp;                              /* Prevent compiler warnings */
        __HAL_DMA_DISABLE(&hdma_usart1_rx);
    }    
    return;
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
 
  /* USER CODE END USART1_IRQn 1 */
}
cs
 
UART1 의 DMA 인터럽트는 DMA2_Stream2_IRQHandler 함수를 사용한다.
코드는 다음과 같다.
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
49
50
51
52
53
54
55
56
void DMA2_Stream2_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream2_IRQn 0 */
    size_t len, tocopy;
    uint8_t* ptr;
    
    /* Check transfer complete flag */
    if (DMA2->LISR & DMA_FLAG_TCIF2_6) {
        DMA2->LIFCR = DMA_FLAG_TCIF2_6;           /* Clear transfer complete flag */
        
        /* Calculate number of bytes actually transfered by DMA so far */
        /**
         * Transfer could be completed by 2 events:
         *  - All data actually transfered (NDTR = 0)
         *  - Stream disabled inside USART IDLE line detected interrupt (NDTR != 0)
         */
        len = DMA_RX_BUFFER_SIZE - DMA2_Stream2->NDTR;
        tocopy = UART_BUFFER_SIZE - Write;      /* Get number of bytes we can copy to the end of buffer */
        
        /* Check how many bytes to copy */
        if (tocopy > len) {
            tocopy = len;
        }
        
        /* Write received data for UART main buffer for manipulation later */
        ptr = DMA_RX_Buffer;
        memcpy(&UART_Buffer[Write], ptr, tocopy);   /* Copy first part */
                
        
        /* Correct values for remaining data */
        Write += tocopy;
        len -= tocopy;
        ptr += tocopy;
        
        /* If still data to write for beginning of buffer */
        if (len) {
            memcpy(&UART_Buffer[0], ptr, len);      /* Don't care if we override Read pointer now */
            Write = len;
        }
        
        /* Prepare DMA for next transfer */
        /* Important! DMA stream won't start if all flags are not cleared first */
        //DMA2->HIFCR = DMA_FLAG_DMEIF1_5 | DMA_FLAG_FEIF1_5 | DMA_FLAG_HTIF1_5 | DMA_FLAG_TCIF1_5 | DMA_FLAG_TEIF1_5;
        DMA2->LIFCR = DMA_FLAG_DMEIF2_6 | DMA_FLAG_FEIF2_6 | DMA_FLAG_HTIF2_6 | DMA_FLAG_TCIF2_6 | DMA_FLAG_TEIF2_6;
        DMA2_Stream2->M0AR = (uint32_t)DMA_RX_Buffer;
        DMA2_Stream2->NDTR = DMA_RX_BUFFER_SIZE;
        __HAL_DMA_ENABLE(&hdma_usart1_rx);
        //DMA1_Stream5->CR |= DMA_SxCR_EN;            /* Start DMA transfer */
    }
    return;
  /* USER CODE END DMA2_Stream2_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart1_rx);
  /* USER CODE BEGIN DMA2_Stream2_IRQn 1 */
 
  /* USER CODE END DMA2_Stream2_IRQn 1 */
}
cs

main() 함수 코드 내용은 다음과 같습니다.
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
 
  /* USER CODE BEGIN 2 */
    __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
    HAL_UART_Receive_DMA(&huart1,DMA_RX_Buffer,DMA_RX_BUFFER_SIZE);
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
        if (Read != Write)
        { 
            __wfi();    // or __wfe();
            //__HAL_UNLOCK(&huart2);
            //__HAL_LOCK(&huart2);
            while (!(USART1->SR & USART_SR_TXE));   /* Wait till finished */
            USART1->DR = UART_Buffer[Read++];
            //HAL_UART_Transmit(&huart2,(uint8_t *)UART_Buffer[Read++],1,1);
            //while (!(USART2->SR & USART_SR_TC));   /* Wait till finished */
            //while (!(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE))){};
                    
                    
            if (Read > UART_BUFFER_SIZE) /* Check buffer overflow */
            {     
                    Read = 0;
            }
        }
        //while(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == GPIO_PIN_RESET);
        //while(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == GPIO_PIN_SET);
        
  }
  /* USER CODE END 3 */
 
}
cs


이전 글에 이어서, 프로그램 동작 테스트를 해 보겠습니다.


CUBEMX에 의해 생성된 기본 코드에서, 추가할 내용은 다음과 같습니다.
[main.c]
1. UART_IT_IDLE 인터럽트 설정.
2. UART RX DMA 설정.

[stm32f4xx_it.c]
1. USART2_IRQHandler 함수에서, HAL_UART_IRQHandler 를 사용하지 않고(너무 느림), 
   IDLE 인터럽트만 처리하도록 수정. IDLE 인터럽트 발생시, DMA rx 인터럽트 강제 발생 후, DMA Disable.
2. DMA1_Stream5_IRQHandler 함수에서 HAL_DMA_IRQHandler 함수를 쓰지 않고,
   FIFO 에서 데이터를 사용자 UART 버퍼로 copy 해온 후, DMA Enable.


다음은 위의 내용에 대한 상세 설명 입니다.
[main.c]
1. UART_IT_IDLE 인터럽트 설정.
2. UART RX DMA 설정.
[기본 변수, 및 define 설정 내용]
1
2
3
4
5
6
7
#define UART_BUFFER_SIZE         1024
#define DMA_RX_BUFFER_SIZE         1024
 
uint8_t UART_Buffer[UART_BUFFER_SIZE];
uint8_t DMA_RX_Buffer[DMA_RX_BUFFER_SIZE];
size_t Write, Read;
 
cs
버퍼 Size 는 대충 잡은 것이니 적당히 수정하시기 바랍니다. 많으면 스택하고 메모리가 많이 들어가니까요.
[설정 코드]
1
2
    __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
    HAL_UART_Receive_DMA(&huart2,DMA_RX_Buffer,DMA_RX_BUFFER_SIZE);
cs
링크 건, 블로그는 정말 복잡한데, HAL 로 하니 2줄이면 끝. (CUBEMX가 알아서 설정해 줘서 그렇습니다)

[stm32f4xx_it.c]
1. USART2_IRQHandler 함수에서, HAL_UART_IRQHandler 를 사용하지 않고(너무 느림), 
   IDLE 인터럽트만 처리하도록 수정. IDLE 인터럽트 발생시, DMA rx 인터럽트 강제 발생 후, DMA Disable.
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
/**
* @brief This function handles USART2 global interrupt.
*/
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
    /* Check for IDLE flag */
    if (USART2->SR & UART_FLAG_IDLE) {         /* We want IDLE flag only */
        /* This part is important */
        /* Clear IDLE flag by reading status register first */
        /* And follow by reading data register */
        volatile uint32_t tmp;                  /* Must be volatile to prevent optimizations */
        tmp = USART2->SR;                       /* Read status register */
        tmp = USART2->DR;                       /* Read data register */
        (void)tmp;                              /* Prevent compiler warnings */
                __HAL_DMA_DISABLE(&hdma_usart2_rx);
 
        //DMA1_Stream5->CR &= ~DMA_SxCR_EN;       /* Disabling DMA will force transfer complete interrupt if enabled */
    }    
    return;
  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
 
  /* USER CODE END USART2_IRQn 1 */
}
cs

위에서 HAL_UART_IRQHandler() 함수 전에 return; 을 사용해주면, 
간단히 이후의 코드 실행을 막을 수 있습니다. ^^

2. DMA1_Stream5_IRQHandler 함수에서 HAL_DMA_IRQHandler 함수를 쓰지 않고,
   FIFO 에서 데이터를 사용자 UART 버퍼로 copy 해온 후, DMA Enable.
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
49
50
51
52
53
54
55
56
57
/**
* @brief This function handles DMA1 stream5 global interrupt.
*/
void DMA1_Stream5_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Stream5_IRQn 0 */
    size_t len, tocopy;
    uint8_t* ptr;
    
    /* Check transfer complete flag */
    if (DMA1->HISR & DMA_FLAG_TCIF1_5) {
        DMA1->HIFCR = DMA_FLAG_TCIF1_5;           /* Clear transfer complete flag */
        
        /* Calculate number of bytes actually transfered by DMA so far */
        /**
         * Transfer could be completed by 2 events:
         *  - All data actually transfered (NDTR = 0)
         *  - Stream disabled inside USART IDLE line detected interrupt (NDTR != 0)
         */
        len = DMA_RX_BUFFER_SIZE - DMA1_Stream5->NDTR;
        tocopy = UART_BUFFER_SIZE - Write;      /* Get number of bytes we can copy to the end of buffer */
        
        /* Check how many bytes to copy */
        if (tocopy > len) {
            tocopy = len;
        }
        
        /* Write received data for UART main buffer for manipulation later */
        ptr = DMA_RX_Buffer;
        memcpy(&UART_Buffer[Write], ptr, tocopy);   /* Copy first part */
        
        /* Correct values for remaining data */
        Write += tocopy;
        len -= tocopy;
        ptr += tocopy;
        
        /* If still data to write for beginning of buffer */
        if (len) {
            memcpy(&UART_Buffer[0], ptr, len);      /* Don't care if we override Read pointer now */
            Write = len;
        }
        
        /* Prepare DMA for next transfer */
        /* Important! DMA stream won't start if all flags are not cleared first */
                DMA1->HIFCR = DMA_FLAG_DMEIF1_5 | DMA_FLAG_FEIF1_5 | DMA_FLAG_HTIF1_5 | DMA_FLAG_TCIF1_5 | DMA_FLAG_TEIF1_5;
                DMA1_Stream5->M0AR = (uint32_t)DMA_RX_Buffer;
                DMA1_Stream5->NDTR = DMA_RX_BUFFER_SIZE;
                __HAL_DMA_ENABLE(&hdma_usart2_rx);
        //DMA1_Stream5->CR |= DMA_SxCR_EN;            /* Start DMA transfer */
    }
        return;
  /* USER CODE END DMA1_Stream5_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart2_rx);
  /* USER CODE BEGIN DMA1_Stream5_IRQn 1 */
 
  /* USER CODE END DMA1_Stream5_IRQn 1 */
}
cs


이렇게 하면, STM32 의 UART RX 에서 FIFO 가 없어서, 데이터를 놓칠 걱정은 더 이상 하지 않아도 됩니다. ^^

주의할 점이 하나 있는데, IDLE 인터럽트를 걸리도록 데이터를 PC 나 다른 장치에서 송신해야 한다는 점입니다.
파일을 전송하는 테스트를 진행해 본 결과, 
DMA RX Buffer 보다 큰 크기와 설정한 DMA 길이 보다 큰 데이터 스트링을 보낼 경우는 다음과 같이, 
송신하는 측에서 딜레이를 약간 줘야 합니다.

그래야, STM32 에서 DMA 데이터를 USER buff에 copy할 수 있습니다. 
저는 tera term 에서 line 당 지연을 1ms 줬습니다.







main.c 의 main() 에서 받은 데이터가 있으면, 그대로 term에 뿌리도록 해서,
파일을 전송 해 보니, 잘 수신 되었음을 알 수 있었습니다.





또 하나, 정말 모르겠던 부분이 있습니다.
HAL_UART_Transmit 함수로 사용해도 되긴 하는데, 너무 내부 코드가 많아서 바꿔 봤는데,
바꾼 코드가 아무리 해도 실행이 안되서, 앞에 __wfi() 나 __wfe() 를 넣어 보니 잘 돌아가더라고요.
순전히 ,  여러가지 넣어 보면서 허송 세월을 보내 버렸네요.
왜 이렇게 되는지 혹시 아시는분 댓글 부탁합니다. 
1
2
3
__wfi();    // or __wfe();
while (!(USART2->SR & USART_SR_TXE));   /* Wait till finished */
USART2->DR = UART_Buffer[Read++];
cs

위와 동일한 코드
1
HAL_UART_Transmit(&huart2,(uint8_t *)UART_Buffer[Read++],1,1);
cs


전체 main() 함수 내용.
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
 
  /* USER CODE BEGIN 2 */
    __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
    HAL_UART_Receive_DMA(&huart2,DMA_RX_Buffer,DMA_RX_BUFFER_SIZE);
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
        if (Read != Write)
        { 
            __wfi();    // or __wfe();
            //__HAL_UNLOCK(&huart2);
            //__HAL_LOCK(&huart2);
            while (!(USART2->SR & USART_SR_TXE));   /* Wait till finished */
            USART2->DR = UART_Buffer[Read++];
            //HAL_UART_Transmit(&huart2,(uint8_t *)UART_Buffer[Read++],1,1);
            //while (!(USART2->SR & USART_SR_TC));   /* Wait till finished */
            //while (!(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE))){};
                    
                    
            if (Read > UART_BUFFER_SIZE) /* Check buffer overflow */
            {     
                    Read = 0;
            }
        }
        //while(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == GPIO_PIN_RESET);
        //while(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == GPIO_PIN_SET);
        
  }
  /* USER CODE END 3 */
 
}
cs


keil 5 로 만든 코드와, cubemx ioc 파일을 첨부합니다.

uart_rx_dma.zip


휴, 밤새 테스트해보다 겨우 됐네요.


이번에 다룰 내용은, STM32F4xx 의 uart Rx 에서 DMA 함수를 사용하면 발생하는 불편함을 수정하는 내용입니다.
DMA를 사용하면, RX data를 수신할 때, 1-Byte마다 인터럽트가 걸리지 않으므로 그 시간만큼 다른 일을 할 수가 있습니다.
일종의 코프로세서라고 말하는 사람도 있습니다. 또는 듀얼코어..

그런데 UART DMA RX 는 HAL 함수에서, 또는 다른 라이브러리 함수에서(다른 함수들은 안써봐서 잘 모름),
지정한 길이 만큼만 받아야 인터럽트가 발생해서,
지정한 길이만큼 받지 않으면 데이터를 갖고 올 수가 없었습니다.

또한 UART RX 시에는 언제, 몇개의 데이터가 들어 올지 미리 알 수가 없는 상황이 대부분이라,
저 같은 경우에는 UART RX DMA를 쓰는 경우는 전혀 없습니다. 쓰려면 TX DMA 를 쓰죠.

예를 들자면 
HAL_UART_Receive_DMA(&huart2,DMA_RX_Buffer,10); 
위와같이 특정 개수 만큼(10개) DMA 로 받겠다고 하면, 
10개 이하는 DMA 인터럽트가 안 떠서 데이터를 못 받고,
10개 이상 받으면, 10개는 받고 나머지 10개 이상은 받지 못하게 됩니다.

이와 같은 점을 수정한 방법이 UART_IDLE 인터럽트를 쓰는 방법입니다.
UART_IDLE 인터럽트는 데이터를 수신하다가 1개 이상 데이터가 수신되지 않을 시 걸리는 인터럽트입니다.
보통 연속으로 데이터가 오다가, 다 보냈을 경우 1개 이상의 데이터가 수신되지 않는 경우가 무조건 발생하게 됩니다.
이 때, UART RX DMA 인터럽트를 강제로 발생시키도록 하는 원리 입니다.

이 내용은, 예전부터 외국 블로그에서 공개한 내용인데 실제로 사용할 수 있도록 테스트를 해 보지 못했습니다.
참고로 한 사이트 주소는 다음과 같습니다.


사용한 H/W 는 ST 의 NUCLEO-F411RE 입니다.
UART만 테스트 하는 거라, 이미 내부 디버거(ST-LINK)에 의해 USB-to-UART 연결된 UART2를 사용했습니다.

H/W 구성 및 CUBEMX 설정은 다음과 같습니다.




+ Recent posts