STM32CubeMonitorはマイコン内部の変数の値をオシロスコープの波形のように表示することが出来るPCアプリです
以前Qiitaの記事で似たようなことを書いたのですが、今回はそのPlatformIO版です
なお、今回使用したボードはnucreo-G474REで以下記事で紹介しているSTU32CubeMX+PlatformIOの開発環境です

STU32CubeMonitorのインストール
CubeMonitorはSTマイクロの公式ページよりダウンロードできます
無料ですがメールアドレス等の登録が必要となります
特に難しいことはなくただインストールすればOKだと思います
プロジェクトの設定
CubeMonitorはSTLinkと通信して実行されるようなのでプロジェクト側で特別何か設定する必要はありません
ただし、ビルドタイプをdebugに設定しておく必要がありますのでplatformio.iniに”build_type”のオプションを追加します
[env:nucleo_g474re]
platform = ststm32
board = nucleo_g474re
framework = stm32cube
build_type = debug
build_flags = -Wl,-Map=.pio/build/nucleo_g474re/farmware.map
board_build.ldscript = STM32G474RETX_FLASH.ld
extra_scripts = pre:extra_script.py
monitor_speed = 115200
[platformio]
include_dir = Core/Inc
src_dir = Core/Src
テスト用コードの作成
テスト用に一次遅れデジタルフィルタのステップ応答波形をCubeMonitorで表示させてみようと思います
一次遅れデジタルフィルタは下記式を使用します

yは出力、xは入力、Tsはサンプリング時間、Tcは時定数です
デジタルフィルタの処理は一定のサンプリング時間で行う必要があるため、timによる定時処理を追加します
今回はtim6にて1ms間隔で割り込みを発生させるようにしました


1ms間隔で割り込み処理を行うため、HAL_TIM_PeriodElapsedCallback関数を作成します
この関数内で先ほどのデジタルフィルタの数式の演算処理を行います
今回はmain.c内に記述しましたが割り込み処理は別ファイルにした方が良いかもです
// TIM周期割り込み処理
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if( htim == &htim6 )
{
output += (input - output) * (0.001f) * ftc; // ftc = 1/timeConst
}
}
時定数(Tc)は色々なパターンをテスト出来るようにシリアル通信で設定するようにしました
通信は下記記事で作成した標準入出力(printf、scanf)を利用します

ブロッキング処理になのでこちらはメインループ内に記述します
また、処理のシーケンスは以下の通りとします
- 時定数入力待ち(scanf)
- 時定数が入力されたらinputを0->1
- 何らかのキー入力待ち(getchar)
- inputを1->0して①に戻る
最終的にmain.cは以下の通りとなりました
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#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 f getc(FILE* f)
#endif
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
float output = 0.0f, input = 0.0f, ftc = 0.0f;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
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_LPUART1_UART_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
setbuf( stdout, NULL );
setbuf( stdin, NULL );
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
float timeConst;
while (1)
{
input = 0.0f;
printf("\ntime constant[s] = ");
scanf("%f", &timeConst);
scanf("%*c");
ftc = 1.0f/timeConst;
input = 1.0f;
printf("input: 0->1\n");
printf("hit any key to end\n");
getchar();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
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 = RCC_PLLM_DIV4;
RCC_OscInitStruct.PLL.PLLN = 85;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
void __io_putchar(uint8_t ch)
{
HAL_UART_Transmit(&hlpuart1, &ch, 1, 0xFFFFFFFF);
}
int __io_getchar(void)
{
uint8_t rxBuf;
while(HAL_UART_Receive(&hlpuart1, &rxBuf, 1, 0xFFFFFFFF) != HAL_OK);
return(rxBuf);
}
// TIM周期割り込み処理
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if( htim == &htim6 )
{
output += (input - output) * (0.001f) * ftc; // ftc = 1/timeConst
}
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
STU32CubeMonitorの設定
CubeMonitorを開いて設定を行います


上の状態まで設定したらボードをUSBでPCに接続し、最初のフロー画面の「myProbe_In」および「myProbe_Out」から接続しているSTLinkを選択します


その後、右上の「DEPLOY」をクリックし、「DASHBOARD」を選択すると波形表示画面に遷移します

同時にtera termを開いて通信処理を行います
とりあえず適当に時定数0.1秒にて実行してみます


それっぽいステップ応答波形が表示できました