이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.


앞의 글 STM32F407로 ESP8266 제어하기 - 수신 인터럽트 사용에서 STM32F407을 경유해서 ESP8266을 제어하는 프로젝트를 완성했습니다. 그러나 이 프로젝트는 STM32 HAL 드라이버가 제공하는 기능을 그대로 사용했습니다만, 약간의 문제를 가지고 있습니다. USART2와 USART3에서 거의 동시에 데이터를 수신하는 경우에 한쪽의 데이터를 잡지 못하는 문제가 간간히 발생합니다.



USART2와 USART3가 속해 있는 버스 APB2의 동작 속도가 낮을수록 이런 현상이 일어나는 회수가 많아집니다. 이를 방지하기 위해서STM32F407로 ESP8266 제어 프로젝트 만들기에서 [Clock Configuration] 탭에서 HCLK 항목을 48MHz로 설정했습니다. HCLK를 48MHz로 설정하면 USART2와 USART3가 달려 있는 버스 APB1의 동작 속도가 24MHz가 됩니다. 그러나 버스의 동작 속도를 높이는 것이 근본적인 해결책이 될 수 없으며, 24MHz로 동작할 때에도 간간히 데이터를 받지 못하여 동작이 중지되는 현상이 발생합니다. 16MHz로 동작하는 AVR에서도 일어나지 않는 현상입니다.

이 문제는 HAL 드라이버가 가정한 상황과 맞지 않는 프로그램을 작성하고 있기 때문일 것입니다. HAL 드라이버가 더 범용적인 용도로 사용할 수 있게 개선되어야 한다고 생각합니다.

인터넷을 검색하다가 이 문제를 해결할 수 있는 글을 찾았습니다(https://www.basic4mcu.com/bbs/board.php?bo_table=d1&wr_id=40&page=3). 일정 수 만큼의 데이터를 수신하고 더 이상 인터럽트가 발생하지 않도록 조치를 취하는 것은 HAL 드라이버이므로, 인터럽트가 발생했을 때에 HAL 드라이버가 호출되지 않도록 하면 됩니다.


USART2 데이터 수신 인터럽트가 발생했을 때에 호출되는 함수는 USART2_IRQHandler()이고, USART3 데이터 수신 인터럽트가 발생했을 때에 호출되는 함수는 USART3_IRQHandler()입니다. 이 두 함수는 모두 [Src\stm32f4xx_it.c] 파일 안에 있습니다. 이 함수들의 원형은 다음과 같습니다.

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}

void USART3_IRQHandler(void)
{
  /* USER CODE BEGIN USART3_IRQn 0 */

  /* USER CODE END USART3_IRQn 0 */
  HAL_UART_IRQHandler(&huart3);
  /* USER CODE BEGIN USART3_IRQn 1 */

  /* USER CODE END USART3_IRQn 1 */
}


USART2와 USART3의 인터럽트 핸들러에서 HAL_UART_IRQ_IRQHandler() 함수를 호출하는 것을 볼 수 있습니다. HAL_UART_IRQ_IRQHandler() 함수를 호출하지 않고 원하는 기능을 수행하도록 USART2_IRQHandler() 함수를 수정합니다.
void USART2_IRQHandler(void)
{
 /* USER CODE BEGIN USART2_IRQn 0 */
   if ((__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) != RESET))
    {
    	PutDataToUartQueue(&huart2, (uint8_t)(huart2.Instance->DR & (uint8_t)0x00FF));
    }
    __HAL_UART_CLEAR_PEFLAG(&huart2); /* clear event flag */
    return;
  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
 }


마찬가지로 USART3_IRQHandler() 함수도 수정합니다.
void USART3_IRQHandler(void)
{
 /* USER CODE BEGIN USART3_IRQn 0 */
   if ((__HAL_UART_GET_FLAG(&huart3, UART_FLAG_RXNE) != RESET))
    {
    	PutDataToUartQueue(&huart3, (uint8_t)(huart3.Instance->DR & (uint8_t)0x00FF));
    }
    __HAL_UART_CLEAR_PEFLAG(&huart3); /* clear event flag */
    return;
  /* USER CODE END USART3_IRQn 0 */
  HAL_UART_IRQHandler(&huart3);
  /* USER CODE BEGIN USART3_IRQn 1 */

  /* USER CODE END USART3_IRQn 1 */
 }



두 함수에서 호출하는 함수 PutDataToUartQueue() 함수는 uart.c 파일에 작성합니다. PutDataToUartQueue() 함수는 수신한 데이터를 각각의 queue에 넣는 일을 합니다.

void PutDataToUartQueue(UART_HandleTypeDef *huart, uint8_t data)
{
	pUARTQUEUE pQueue = (huart->Instance == USART2 ? &WifiQueue:&MonitorQueue);
	if (pQueue->data == QUEUE_BUFFER_LENGTH)
		GetDataFromUartQueue(huart);
	pQueue->Buffer[pQueue->head++] = data;
	if (pQueue->head == QUEUE_BUFFER_LENGTH) pQueue->head = 0;
	pQueue->data++;
}


더이상 HAL_UART_IRQHandler() 함수를 호출하지 않기 때문에 uart.c 안에 만들었던 callback 함수 HAL_UART_RxCpltCallback()은 삭제합니다. 다음은 수정한 uart.c입니다.

#include "stm32f4xx_hal.h"

#include "uart.h"

UARTQUEUE WifiQueue;
UARTQUEUE MonitorQueue;

void InitUartQueue(pUARTQUEUE pQueue)
{
	pQueue->data = pQueue->head = pQueue->tail = 0;
}

void PutDataToUartQueue(UART_HandleTypeDef *huart, uint8_t data)
{
	pUARTQUEUE pQueue = (huart->Instance == USART2 ? &WifiQueue:&MonitorQueue);
	if (pQueue->data == QUEUE_BUFFER_LENGTH)
		GetDataFromUartQueue(huart);
	pQueue->Buffer[pQueue->head++] = data;
	if (pQueue->head == QUEUE_BUFFER_LENGTH) pQueue->head = 0;
	pQueue->data++;
}

void GetDataFromUartQueue(UART_HandleTypeDef *huart)
{
	UART_HandleTypeDef *dst = (huart->Instance == USART2 ? &hMonitor:&hWifi);
	pUARTQUEUE pQueue = (huart->Instance == USART2 ? &WifiQueue:&MonitorQueue);
	if (HAL_UART_Transmit(dst, pQueue->Buffer + pQueue->tail, 1, 3000) != HAL_OK)
	{
	    _Error_Handler(__FILE__, __LINE__);
	}
	pQueue->tail++;
	if (pQueue->tail == QUEUE_BUFFER_LENGTH) pQueue->tail = 0;
	pQueue->data--;
	HAL_Delay(1);
}


다음은 수정한 uart.h입니다. 이전 글의 내용 외에 uart.h에 PutDataToUartQueue() 함수 원형만 추가하였습니다

#ifndef UART_H_
#define UART_H_

#define hWifi    huart2
#define hMonitor huart3

#define QUEUE_BUFFER_LENGTH 1024

typedef struct
{
	int head, tail, data;
	uint8_t Buffer[QUEUE_BUFFER_LENGTH];
}UARTQUEUE, *pUARTQUEUE;

extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3;

extern UARTQUEUE WifiQueue;
extern UARTQUEUE MonitorQueue;

void InitUartQueue(pUARTQUEUE pQueue);
void PutDataToUartQueue(UART_HandleTypeDef *huart, uint8_t data);
void GetDataFromUartQueue(UART_HandleTypeDef *huart);

#endif /* UART_H_ */



컴파일 시에 오류가 발생하지 않도록 [Src\stm32f4xx_it.c] 파일에 다음과 같이 uart.h를 포함시킵니다. #include 문이 든 한 행만 추가로 입력합니다. 주석 처리된 다른 행들은 STM32CubeMx가 만들어 준 것으로 입력할 위치를 보여주기 위하여 같이 기술하였습니다.


/* USER CODE BEGIN 0 */
#include "uart.h"
/* USER CODE END 0 */



이렇게 프로그램을 수정한 후에는 간간히 데이터를 받지 못하던 문제가 해결된 것 같습니다. APB1 버스 속도를 2MHz로 낮추고 동작시켜도 수신 문자를 빠뜨리는 현상은 발생하지 않습니다.

프로젝트에서 사용한 파일들을 첨부합니다.

 

STM32F407Uart.zip




블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,