지난 번의 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 설정은 다음과 같습니다.




이번에 사용하는 보안 칩이 I2C로 되어 있어서, I2C 통신에 대해서 써 보겠습니다.


예전에 적은 글이 너무 허접해서, 다른분들이 봐도 도움이 안될 듯 해서 다시 한번 써 봅니다.

회사에 아주 많이 굴러다니는 F4 개발 키트중에 골라 잡으니, STM32F411(NUCLEO-F411RE) 이네요.
일단 요놈에다가 I2C 로 보안칩을 연결했습니다.

다음은 STM32F411 NUCLEO 보드의 핀팹을 CUBEMX에서 설정한 내용입니다.


 
다음은 NUCLEO 보드의 I2C 포트의 위치를 표시한 그림입니다. 개발 보드에서 최대한 모여있는 위치로 , CUBEMX 에서 핀을 선택하고 이동했습니다. UART2 는 USB-to-UART 로 NUCLEO 보드와 연결되어 있어서 디버깅 하기가 좋습니다. ^^





이렇게 I2C 에 보안칩을 연결해 봤습니다. 작아서 , 큰 선들을 연결하기 힘들었습니다.




보안칩 핀 넘버 .





다음은 CUBEMX 의 CLK 설정입니다. NUCLEO 보드의 입력 클럭이 8MHz 이므로, HSE 를 선택하고, 최종 HCLK는 100MHz





이제 I2C 설정을 해 보겠습니다. configuration 에서 I2C1을 클릭.





I2C1 설정 중, Parameter Setting 에서 수정한 것은 Clock speed 입니다. 보안 칩이 400KHz 까지 지원을 한다는데, 
오실로 스코프 파형이 별로라서 50KHz로 설정. ^^





다음은 DMA를 설정 합니다.
I2C1_TX , I2C1_RX 둘 다 선택합니다.




다음은 인터럽트설정 입니다. 여기서 주의할 점은 , I2C1_event interrupt 와 I2C1_error interrupt 2개도 선택해 줘야,
나중에 s/w 에서 DMA 함수가 동작합니다. 왜냐하면, DMA 함수에서 인터럽트 함수를 쓰고 있습니다.





다음은, GPIO 설정입니다. 포트를 PULL UP 해 줍니다. 이렇게 하면, 따로 풀업 저항이 필요 없습니다.




다음은 인터럽트를 설정해 보겠습니다. NVIC 를 클릭 합니다.





인터럽트 설정의 우선 순위를 정합니다. 아직 아무엏게나 번호를 붙이고 있습니다. 
나중에 된통 당해봐야 , 어떻게 번호를 붙이는 줄 알 것 같은데.. 막 나눠 주는 중입니다. ^^





마지막으로 프로젝트 설정입니다. 그냥 저장 위치하고, 프로젝트 이름, 컴파일러 종류 등을 설정해 줍니다.




이렇게 H/W 와 I2C DMA 프로그램의 설정은 완료 되었고, 글이 길어지니까.. I2C S/W 테스트는 다음에 올리겠습니다.


저도 예전부터 고민하던 내용인데,

UART 는 TX는 고민 거리가 아니죠. 
내보낼 데이터 개수는 미리 알 수 있어서 DMA로 보내면 CPU에 무리를 안주고 보낼 수 있으니까요.

그런데, 다양한 프로토콜이 있어서 길이가 재각각이거나, 데이터가 깨져서 길이(데이터 개수)가 틀려질 경우,
DMA 로 수신시에 받을 데이터의 개수를 알 수 없죠.
DMA는 미리 '몇 개 받겠다' 라고 선언하고 실행하도록 되어 있습니다. - 덜 받거나, 더 받으면 문제가 생기죠.

이와 같은 부분을 해결하는 솔루션이 다음 링크에 나와 있습니다.
시간이 안되서 못 해보는데, 사용할 생각 입니다. ^^ 


I2C 통신으로 간단하게 EEPROM(AT24C04) 를 읽기/쓰기 테스트 해 봤습니다.


예전에 PSOC4로 해 놓은 자료가 있으니까, 자세한 설명은 빼고 동작 테스트만 해 보겠습니다.

먼저 I2C 인터럽트를 사용하여 데이터를 보내고 받는 함수들은 다음과 같습니다. 
1. HAL_I2C_Master_Transmit_IT()
   : I2C 인터럽트를 사용하여 데이터를 보내는(Write) 함수
2. HAL_I2C_MasterTxCpltCallback()
   : I2C 인터럽트를 사용하여 데이터를 보내고 다 보내면 인터럽트에 의해 호출되는 함수
3. HAL_I2C_Master_Receive_IT()
   : I2C 인터럽트를 사용하여 데이터를 받는(Receive) 함수
4. HAL_I2C_MasterRxCpltCallback()
   : I2C 인터럽트를 사용하여 정해진 개수의 데이터를 다 받으면 인터럽트에 의해 호출되는 함수

위의 함수들은 stm32f4xx_hal_i2c.c 파일에 정의되어 있습니다. 
HAL_I2C_MasterTxCpltCallback(), HAL_I2C_MasterRxCpltCallback() 과 같은 콜백함수는 __weak 키워드를 없애고
main.c 파일에 복사해서 내부 내용을 바꿔서 쓰면 인터럽트가 걸릴때 마다 콜벡함수를 호출하게 됩니다.

이 파일의 위치는 다음과 같습니다.



친구 돌잔치를 가봐야 해서 main.c 에서 EEPROM 읽고 쓰는 부분을 그림으로 올려 놓겠습니다.







[16 Byte Write 오실로 스코프 파형]





[Word Address Write 오실로 스코프 파형]





[16 Byte Read 오실로 스코프 파형]



소스 파일 첨부합니다.

I2C_test (2).zip


이번에는, 이전 소스 코드에, UART2 인터럽트 처리 함수를 추가해 봤습니다.


STM32F4xx 의 UART 데이터 처림 방식에는 3가지가 있습니다.
1. Polling mode
 보내고 받는 동작에서 다른 일을 못하고 상태를 지켜 보고 있어야 해서 효율이 떨어진다.
2. Interrupt mode
 보내고 받는 동작이 완료되면, 인터럽트 함수로 점프해서 특정 동작을 처리하고 돌아온다.
보낼 경우는 몇개의 데이터를 보내는지 알아서 딜레이를 주던가, 주기적으로 상태를 체크하면서 동작 시키면 되는데,
받을 경우에는 가변 길이의 프로토콜일 경우 1개의 데이터가 받아질 때마다 인터럽트 처리를 해야 하는데 
데이터 전송률이 높을 수록, 또 수신 데이터가 많아질 수록 함수를 자주 호출하므로 다른일을 못하게 되어 효율이 떨어진다.
3. DMA mode
가장 효율이 좋은 방식으로, 인터럽트 처리 함수를 호출할 필요가 없이 메모리에 직접 데이터가 받아지므로,
주기적으로 받은 데이터를 링버퍼에 쌓아서 처리하면 된다.
외국에서 DMA방식을 써서 많은 잇점이 있다고 하는 글을 첨부해 봅니다.

DMA allows the UART to store data in memory WITHOUT interrupting the processor until the buffer is full saving lots of overhead.

In my case, I told PDC to store UART data into an buffer (byte array) and specified the length. When UART via PDC(Peripheral DMA controller) filled the buffer the PDC issued an interrupt.

In PDC ISR:

  1. Give PDC new empty buffer
  2. Restart UART PDC (so can collect data while we do other stuff in isr)
  3. memcpy full buffer into RINGBUFFER
  4. Exit ISR

As swineone recommended above, implement DMA and you'll love life.


저는 DMA는 링버퍼 구현 때문에 시간이 좀 걸릴 것 같아서, 
우선은 다른 구현해야할 동작을 먼저 하고 일단은 RX 인터럽트만 동작을 시켜봤습니다.

이전에 Cube툴에서 UART2 인터럽트를 설정해 놓고 코드를 생성했기 때문에,
이전 글에서 첨부한 소스코드에 Rx 인터럽트 처리함수만 추가해서 1바이트 받으면 바로 TX로 뿌려주는 동작만 구현했습니다.

타이머 인터럽트 처리함수처럼 UART인터럽트도 사용자 콜백함수가 있었습니다.
이 함수 역시도 __weak 키워드가 붙어 있고 이것을 사용하려면 사용할 사용자 파일에 복사해서 붙여넣기를 해서
__weak 키워드를 지워주고 함수 안의 내용을 입맛에 맞게 고쳐주면 됩니다.

처음에 할 일은, 설정은 이미 자동으로 코드가 생성되어 완료가 됐고 
1. "내가 uart2 포트(huart2)로 인터럽트 방식을 사용해서 특정 변수에(uart2_rx_ch) 1바이트를 받겠다" 라는 명령을 실행시킵니다.
 HAL_UART_Receive_IT(&huart2,&uart2_rx_ch,1);

2. 위의 코드가 실행되면 uart2로 1바이트의 데이터가 수신될 때마다 인터럽트가 걸리는데,
이 때마다 위의 설명에서 말씀드린 사용자 콜백함수로 점프해서 해당 코드를 실행하고 복귀합니다.
아까 말씀 드린 stm324xx_hal.c 파일에 있던 __weak 키워드가 붙어있는 사용자함수를 main.c 파일에 붙여 넣고 함수 내부 코드를 수정한 함수는 다음과 같습니다.



코드의 내용은 "받은 데이터를 폴링방식으로 TX로 출력하고, 다시 인터럽트로 1바이트 받겠다" 입니다.
일종의 echo 동작이죠.
DMA 까지 필요없는 분들은 이 함수를 수정해서 링버퍼에 쌓아서 쓰시던가 그냥 입맛에 맞게 적당히 고쳐서 쓰시면 되겠습니다.

테스트 결과는 다음과 같습니다.



소스코드 첨부 합니다.

timer3_test2.zip




+ Recent posts