[STM32] printf/scanf implementation in CubeIDE console by UART with USB connection over ST-link

This article explains how to implement printf and scanf in console of CubeIDE like the following image. Target of this article is just beginner in STM32.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/a8983458-bddc-8b4a-1075-90dfbb4d9adb.png)

Communication is conducted by UART with USB connection over ST-link.

Total procedure is composed of the following steps.

- Hardware configuration
- Software configuration
- Terminal configuration

In this article, Nucleo-H743ZI2 is used as an example board.

## Hardware Configuration
At the beginning, it is necessary to identify which pins are capable of UART communication through ST-link.
Check 'USART communication' section in the datasheet of Nucleo board.
The name of each Nucleo board is written on back-side of the board as "MB----".

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/787fdc19-388b-6455-8954-3fd871533966.png)

You can find the user manual by searching "datasheet MB1364B" in this case for example.
In the document UM2407 of MB1364, you can find USART communication section on pp. 26-27.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/1b5433c6-f9bb-d647-d677-9d2f049b33b0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/e7e4f8c4-1dec-6f7b-57ee-89e248ad2565.png)
(STMicroelectronics, User Manual [UM2407](https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf))

Note that you need to confirm settings if your Nucleo has jumpers for ST-link configurations.

Now you have found out *PD8* and *PD9* are the pins for UART over ST-link from the above document (it depends your board). 
According to the datasheet, you can set the pinout for communication in CubeIDE.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/bc3c6de2-8920-b00d-ac04-aecf500c8d59.png)

The configuration of UART is as follows.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/af3917ae-3705-ae23-4fc3-7ef8b7a5fc60.png)

Change Mode to Asynchronous and leave the rest as default.
Keep Basic Parameters for setting terminal later.


## Software Configuration
The next step is implementing printf and scanf in the main.c file.

Read/Write by HAL is possible once UART is activated as the following code.
```
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
```
Those are defined in "stm32xxxx_hal_uart.c" that is located at \Drivers\STM32xxxx_HAL_Driver\Src\ folder when you conduct code generation.

The following steps just will have the compiler connect standard input/output to the UART stream.

First, you need to include "stdio.h" for using printf/scanf in USER CODE Includes in the main.c file.

```
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
```

The following code will have the compiler connect standard output to UART for printf.

```
/* USER CODE BEGIN PFP */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}
/* USER CODE END PFP */
```
Make sure to change the number of UART written as "huart3" in the code according to your configuration.

Also, the following code will have the compiler connect standard input to UART for scanf.

```
/* USER CODE BEGIN PFP */
#ifdef __GNUC__
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif

GETCHAR_PROTOTYPE
{
    uint8_t ch = 0;
    __HAL_UART_CLEAR_OREFLAG(&huart3);
	HAL_UART_Receive(&huart3,(uint8_t *)&ch, 1, HAL_MAX_DELAY);
	HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY); //echo output for console
	return ch;
}
/* USER CODE END PFP */
```
If you plan using both of printf/scanf, then merged code can be used.
```
/* USER CODE BEGIN PFP */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

GETCHAR_PROTOTYPE
{
    uint8_t ch = 0;
    __HAL_UART_CLEAR_OREFLAG(&huart3);
	HAL_UART_Receive(&huart3,(uint8_t *)&ch, 1, HAL_MAX_DELAY);
	HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY); //echo output for console
	return ch;
}
/* USER CODE END PFP */
```


It is known that the default syscalls.c file automatically generated by STM32CubeIDE results in unexpected behavior when internal buffering of the input stream is enabled [1].

To avoid this issue, you need to disable buffering before any call to scanf() as the following code.
```
int main(void)
{
  /* USER CODE BEGIN 1 */
	setvbuf(stdin, NULL, _IONBF, 0); //disable buffering for input stream
...
```

Now you can use printf and scanf by UART.

## Terminal Configuration
Open the device manager and check the COM port of ST-link. In the following case, COM8 is the port.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/0e62de0a-2f56-c24e-42e6-bb9f19f6915e.png)

In the console window, push "open console" button and choose"3 Command Shell Console".

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/297755b7-c7bc-e6aa-0bb8-a7897fbc139d.png)

Set the Connection Type to "Serial Port", then create new connection pushing "New..." button.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/ac29d435-440d-22ea-3bb9-455add11a2f0.png)

Set the all parameters according to hardware configurations.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/4cf90cdc-bd00-6a9a-38f8-b2b7d18789d0.png)

Now you can use printf/scanf and the built-in terminal console in CubeIDE.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/d2f6d303-bfc6-b857-37be-8f699ac2be65.png)


## Example of changing PWM parameter while running
Enable timer and set parameters of it.
The PWM frequency are defined with prescaler (*PSC*) and counter period (*ARR*) as
$$f_\mathrm{PWM} = \frac{f_\mathrm{clock}}{PSC\times (ARR-1)}.$$

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583609/9cd636fa-3658-852e-a8a5-172f5723730d.png)

You can use the following code to activate PWM timer.
```
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
```

The following code allow you to update PWM frequency while runnning.
```
  printf("\033[H\033[J"); // Clear console window
  while (1) {
         printf("Enter new frequency in kHz: ");
         scanf("%d", &f_ref);
         printf("\r\nCurrent frequency: %d kHz\r\n",f_ref);
         TIM1->ARR = (int) f_clock/f_ref-1;
         TIM1->CCR1 = (int) (f_clock/f_ref-1)*duty;

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
```



## Reference
This article were written based on the following forum contents. 
- [1] Easy Use scanf on STM32
[https://forum.digikey.com/t/easily-use-scanf-on-stm32/21103](https://forum.digikey.com/t/easily-use-scanf-on-stm32/21103)
- [2] How to use the STM32CubeIDE terminal to send and receive data
[https://community.st.com/t5/stm32-mcus/how-to-use-the-stm32cubeide-terminal-to-send-and-receive-data/ta-p/49434](https://community.st.com/t5/stm32-mcus/how-to-use-the-stm32cubeide-terminal-to-send-and-receive-data/ta-p/49434)