【STM32(nucleo)】TIMモジュールのエンコーダモード(位相計数モード)の使い方【PlatformIO】

STM32のTIMモジュールのエンコーダモードを試してみましたので使い方を説明します

ルネサスのRXでいうところのMTUの位相計数モードです

使用するボードはnucleo-G474RE、開発環境は下記記事で紹介しているPlatformIO+STM32CubeMXです

STM32(nucleo)開発環境構築(PlatformIO+STM32CubeMX)

STM32CubeMXの設定(TIMモジュール)

まず最初はCubeMXにて周辺機能の設定です

今回取り扱うエンコーダはブラシレスDCモータに搭載されているインクリメンタルエンコーダ(AB相タイプ)です

インクリメンタルエンコーダの説明については色々なサイトでわかりやすく説明されているのでそちらを引用させて頂きます

上記サイトで紹介されているエンコーダは原点を知らせるZ相がありますが、今回使用するものはAB相のみとなります

また、取り込んだカウンタ値はシリアル通信(標準出力)で確認します

シリアル通信の設定については下記記事を参照ください

【STM32(nucleo)】シリアル通信の使い方【PlatformIO】 【STM32(nucleo)】標準入出力(printf、scanf)の設定方法【PlatformIO】
2023.05.09追記

実際のモータ制御にエンコーダを使用する場合、エンコーダのカウンタ周期とTIMモジュールのカウンタ周期を合わせてやる必要があります

今回のモータは300パルス/回転(300×4=1200カウント)なので、Counter Periodに1199をセットします

動作確認用コードの作成

エンコーダモードはブロッキング処理のHAL_TIM_Encoder_Start関数とノンブロッキング処理のHAL_TIM_Encoder_Start_IT関数とHAL_TIM_Encoder_Start_DMA関数があります

しかし、DMAの処理が上手く行きませんでしたので今回の記事はのHAL_TIM_Encoder_Start関数とHAL_TIM_Encoder_Start_IT関数を紹介します

HAL_TIM_Encoder_Start関数を使用した例

HAL_TIM_Encoder_Start関数を実行後、__HAL_TIM_GET_COUNTERでカウンタにアクセスできます

下記例ではカウンタが更新されたらカウンタをシリアル通信(標準出力)で外部に出力する仕様としました

/* 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 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 */

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_TIM3_Init();
  /* USER CODE BEGIN 2 */
  setbuf( stdout, NULL );
  setbuf( stdin, NULL );
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
  uint32_t encCnt = 0;
  while (1)
  {
    if( encCnt != __HAL_TIM_GET_COUNTER(&htim3) )
    {
      encCnt = __HAL_TIM_GET_COUNTER(&htim3);
      printf("%5ld\r",encCnt);
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/* 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); 
}
/* USER CODE END 4 */

いつものようにtera termで確認します

今回はエンコーダを接続する必要があるのでブラシレスDCモータのエンコーダ信号をnucleoのPC6とPC7に接続します

モータのエンコーダは5V仕様でしたが、3.3Vでも動いたのでとりあえず良しとしています

エンコーダの仕様はモータ軸1回転で300パルスなので、1回転で1200カウント検出できればOKと判断します(AB相の立上り/立下りエッジでカウントなので300×4)

今回使ったモータのエンコーダはCCWでA相が先行するの仕様のようなので、CCW方向でカウントアップ、CW方向でカウントダウンとなります(先ほどの参考サイトの例とは逆)

初期状態からCCW方向にモータ軸を1回転させたところ、1200カウントとなりましたのでOKと判断します

2023.05.09追記

TIMモジュールのCounter Periodに1199を設定した場合は1200ではなく0にリセットされます

HAL_TIM_Encoder_Start_IT関数を使用した例

末尾”IT”なので割り込み処理となります

前準備としてSTM32CubeMXで割り込みを許可設定します

カウンタが更新される度に割り込みが発生するようなので下記例では通信処理を割り込みコールバック関数に記述しています

動作としては先ほどと全く同じ挙動となることを確認しました

/* 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 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 */

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_TIM3_Init();
  /* USER CODE BEGIN 2 */
  setbuf( stdout, NULL );
  setbuf( stdin, NULL );
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  HAL_TIM_Encoder_Start_IT(&htim3, TIM_CHANNEL_ALL);
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/* 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); 
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if( htim == &htim3 )
  {
    printf("%5ld\r",__HAL_TIM_GET_COUNTER(&htim3);
  }
}

/* USER CODE END 4 */

index信号(Z相)がある場合の設定

TIMモジュールにはZ相を取り込んでカウンタをリセットする機能がありますので試してみました

まずはCubeMXでEncoder Mode + indexを選択します

次にGPIOの設定でZ相を取り込むポート(今回はPD2)をプルダウンします

TIMモジュールの初期設定ではindex信号はLowからHighになったときに検出されるようなので、通常時Low、検出時Highという論理なので端子をプルダウンします

その他のソースは変更不要です

カウンタUP/DOWN中に手動でPD2をVcc(3.3V)に接続するとカウンタがゼロにクリアされることを確認しました

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA