본 글은 STM32F769I MCU를 기준으로 작성되었습니다.
세부적인 내용은 제품군마다 조금씩 다를 수 있습니다.
stm32에서는 syscall.c 파일 안에 있는 _write, _read 등의 시스템 콜 함수를 재정의함으로써 GNU C의 printf, scanf 함수를 그대로 이용할 수 있다고 한다.
printf와 scanf를 UART1을 통해 사용하도록 설정해보자.
_write 함수를 재정의함으로써 사용할 수 있다.
int _write(int fd, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (unsigned char*)ptr, len, HAL_MAX_DELAY);
return len;
}
주의할 점은 표준 입출력 사용 시 버퍼링 정책을 사용하지 않도록 해주어야 한다.
setvbuf 함수를 이용하여 설정 가능하다.
setvbuf(stdout, NULL, _IONBF, 0);
printf 함수를 통해 UART1로 1초마다 Hello world!를 출력하는 코드를 작성하였다.
#include "main.h"
#include <stdio.h>
UART_HandleTypeDef huart1;
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = { 0, };
RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0, };
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 192;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 4;
RCC_OscInitStruct.PLL.PLLR = 2;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3);
}
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
HAL_UART_Init(&huart1);
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = { 0, };
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
int _write(int fd, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (unsigned char*)ptr, len, HAL_MAX_DELAY);
return len;
}
int main(void)
{
HAL_Init();
MX_GPIO_Init();
MX_USART1_UART_Init();
char *str = "Hello world!";
setvbuf(stdout, NULL, _IONBF, 0);
while (1)
{
printf("%s\r\n", str);
HAL_Delay(1000);
}
}
void Error_Handler(void)
{
}
빌드 후 실행하여 Baudrate 115200으로 터미널을 열면 아래와 같이 1초 주기로 Hello world!가 출력되는 것을 볼 수 있다.
scanf도 마찬가지로 _read 함수를 재정의함으로써 사용할 수 있다.
int _read(int file, char *ptr, int len)
{
HAL_UART_Receive(&huart1, (unsigned char*)ptr, len, HAL_MAX_DELAY);
return len;
}
다만 printf와 달리 __io_putchar, __io_getchar 함수도 같이 재정의해주어야 한다.
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart1, (unsigned char*)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int __io_getchar(void)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
scanf로 입력받은 문자열을 그대로 printf로 출력하는 코드를 작성하였다.
UART1 115200을 이용한다.
#include "main.h"
#include <stdio.h>
UART_HandleTypeDef huart1;
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = { 0, };
RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0, };
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 192;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 4;
RCC_OscInitStruct.PLL.PLLR = 2;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3);
}
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
HAL_UART_Init(&huart1);
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = { 0, };
__HAL_RCC_GPIOA_CLK_ENABLE();
// Initialize USART1
GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
int _write(int fd, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (unsigned char*)ptr, len, HAL_MAX_DELAY);
return len;
}
int _read(int file, char *ptr, int len)
{
HAL_UART_Receive(&huart1, (unsigned char*)ptr, len, HAL_MAX_DELAY);
return len;
}
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart1, (unsigned char*)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int __io_getchar(void)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int main(void)
{
HAL_Init();
MX_GPIO_Init();
MX_USART1_UART_Init();
char str[256] = { 0, };
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
while (1)
{
scanf("%s", str);
printf("%s\r\n", str);
}
}
void Error_Handler(void)
{
}
빌드 후 실행하여 터미널에서 키보드를 입력하면 입력한 내용이 그대로 출력되는 것을 볼 수 있다.
사실 문제점이 몇 개 있는데, 하나는 입력한 문자가 바로바로 터미널에 출력되지 않는 것이며, 또 하나는 스페이스바를 누르면 입력한 문자열이 출력된다는 것이다.
버퍼링 정책 해제하면 되는 건줄 알았는데, 방법이 잘못되었거나 내가 잘못 알고 있는듯... 재확인 필요함.