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


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


+ Recent posts