STM32L010F4P6マイコンを使ってステッピングモーターを動かしてみた

秋月電子通商で買ったバイポーラ ステッピングモーター ST-42BYH1004を動かしてみた。

STM32マイコン STM32L010F4P6
image01
バイポーラ ステッピングモーター ST-42BYH1004 を動かした。
(配線がばらけて見栄えが悪いのでチューブに入れました。)

前回の続きになるはずが…

前回のマイコンを利用するはずが。

image02

電池が切れたから電源装置につないだところ
電圧をよく確認していなかったからおそらく20V位がかかってレギュレータが破裂してマイコンとディスプレイモジュールを道ずれに全部破損した。

LCDをAE-AQM1602に交換

ということで秋月電子通商から新しくマイコンと
I2C接続小型キャラクタLCDモジュール(16×2行・3.3V/5V)ピッチ変換キットを購入した。

image03

aqm1602pinout 出典 : LCDデータシート(参考資料)

キットの説明書を読んでからはんだ付けしてマイコンと資料の通りにつなぐ。

AE-AQM1602 は
STM32L010F4P6マイコンを使ってI2C低電圧キャラクタ液晶モジュール(SB1602B)を動かしてみた
で使った
ストロベリーリナックス I2C低電圧キャラクタ液晶モジュール(16x2行)
と同じコントローラの ST7032i なのと, 同じI2Cアドレスなので

ブレッドボード上でピン配置を合わせて差し替えるだけで
ソフトウェアはそのまま使える。

データシートを確認する

STM32マイコン STM32L010F4P6のデータシートは毎度同じなので省略。

ST-42BYH1004のデータシートを参照する。

ST-42BYH1004_ds

  • 相数: 2
  • 基本ステップ角: 0.9度±5%
  • 1回転ステップ数: 400
  • 入力定格電圧: 5V
  • 定格電流: 1.0A/相

電源は1.2Vニッケル水素電池を4本直列なので, 定格5Vで丁度いいでしょう。

ステッピングモーターの基礎知識

YouTubeと

Tech Web Motor - ROHMで ステッピングモーターの基礎知識を付ける。

ステッピングモータの駆動: バイポーラ結線とユニポーラ結線

ステッピングモータ ST-42BYH1004はバイポーラ結線なので。

tech_web_motor01 出典: https://techweb.rohm.co.jp/motor/knowledge/basics/basics-04/496

2相バイポーラステッピングモータの駆動 その1

tech_web_motor02 出典: https://techweb.rohm.co.jp/motor/knowledge/basics/basics-04/499

2相バイポーラステッピングモータは、Hブリッジ駆動回路を2ch分使用することで駆動できます。このブロック図は、PWM動作により定電流駆動を行う回路の例で、動作は基本的にPWM出力によるDCブラシ付モータの駆動と同じです。

PWM出力によるブラシ付DCモータの駆動:Hブリッジ定電流駆動

tech_web_motor03 出典: https://techweb.rohm.co.jp/motor/knowledge/basics/basics-03/362

KiCADで回路設計

ということでHブリッジ駆動回路を2組用意する。

(お気に入りなんですよ。FETより安いから。)

  • ハイサイドドライブはブートストラップ回路とフォトカプラで絶縁

構成は簡単です。スイッチとコンデンサ、ダイオードで構成される昇圧チャージポンプで、スイッチ電圧(VIN)と内部電圧を足した電圧を上側のNch MOSFETのゲートドライブとして利用します。

出典: https://techweb.rohm.co.jp/knowledge/dcdc/dcdc_sr/dcdc_sr01/829

回路定数は詰める必要がある。(そこにあった部品を使っただけ)

schematics

ブレッドボードで配線する

完成写真。
オシロスコープで見ているとノイズが邪魔だったので, そこにあったフェライトコアを通した。

image04

上がパワー段, 下が制御段。

パワー段の拡大。 image05

breadboard01

breadboard02

STM32CubeIDEでプロジェクトを開く

前回のプロジェクトを再利用する。

  • CubeIDE Version: 1.8.0
  • C++17

STM32L010F4P6の設定

クロックは24MHz screenshot01

ポートAをOutputに screenshot02

2相バイポーラステッピングモーターを動かしてみる

とりあえず

ステッピングモーターをホーム位置から

  1. 90度位置まで時計回り(CW)に移動して1秒停止
  2. 900度位置まで時計回り(CW)に移動して1秒停止
  3. 90度位置まで反時計回り(CCW)に移動して1秒停止
  4. ホーム位置に戻って1秒停止
  5. -90度位置まで反時計回り(CCW)に移動して1秒停止
  6. -900度位置まで反時計回り(CCW)に移動して1秒停止
  7. -90度位置まで時計回り(CW)に移動して1秒停止
  8. ホーム位置に戻って1秒停止

を繰り返すプログラムを書いた。

パルス速度はタイマ割り込みで制御。
割り込みハンドラで計算しているので, パルス速度を上げるにはクロック周波数を上げないと追いつかないかも。
1-2相励磁(halfStepDrive)なので, 400stepで半周動く。

Core/Src/Application.cpp
/*
 * Application.cpp
 *
 * Copyright 2021 Akihiro Yamamoto.
 * Licensed under the Apache License, Version 2.0
 * <https://spdx.org/licenses/Apache-2.0.html>
 *
 */
#include "main.h"

#include <ST7032iLcd.hpp>
#include <array>
#include <cmath>

extern I2C_HandleTypeDef hi2c1;
extern TIM_HandleTypeDef htim2;

static ST7032iLcd i2c_lcd(hi2c1);

using ExcitingACBD = uint8_t;
constexpr const ExcitingACBD ExA = 0b1000;
constexpr const ExcitingACBD ExC = 0b0100;
constexpr const ExcitingACBD ExB = 0b0010;
constexpr const ExcitingACBD ExD = 0b0001;

using HiACBDLoACBD = uint8_t;

static inline HiACBDLoACBD fromExcitingACBD(ExcitingACBD acbd) {
  return (acbd & 0xf) << 4 | (acbd & 0xf);
}

//
static inline void excitingCoil(HiACBDLoACBD hiloACBD) {
  uint32_t odr = GPIOA->ODR;
  if ((odr & 0xff) != hiloACBD) {
    GPIOA->BSRR = (~hiloACBD & 0xff) << 16;
    // clang-format off
    asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
    asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
    // 10
    asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
    asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
    // 20
    // clang-format on
    GPIOA->BSRR = hiloACBD & 0xff;
  }
}

// turn off all transistor
static inline void turnOffAll() { GPIOA->BSRR = 0xff << 16; }

// short brake = turn ON all lower side switch.
static inline void shortBrake() { excitingCoil(0b00001111); }

// Low A (PA0)
static inline void turnOnLowA() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
}
static inline void turnOffLowA() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
}
// Low C (PA1)
static inline void turnOnLowC() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
}
static inline void turnOffLowC() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}
// Low B (PA2)
static inline void turnOnLowB() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
}
static inline void turnOffLowB() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
}
// Low D (PA3)
static inline void turnOnLowD() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);
}
static inline void turnOffLowD() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);
}

// High A (PA4)
static inline void turnOnHighA() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
static inline void turnOffHighA() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
}
// High C (PA5)
static inline void turnOnHighC() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}
static inline void turnOffHighC() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
}
// High B (PA6)
static inline void turnOnHighB() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);
}
static inline void turnOffHighB() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);
}
// High D (PA7)
static inline void turnOnHighD() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
}
static inline void turnOffHighD() {
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
}

enum class Rotation : int8_t { CW = 1, CCW = -1 };

// Wave drive (one phase on)
static inline int32_t waveDrive(int32_t steps, Rotation r) {
  constexpr static const std::array<ExcitingACBD, 4> pulses{
      ExA, // A
      ExB, // B
      ExC, // C
      ExD, // D
  };
  uint32_t x = (pulses.size() + (steps & 3)) & 3;
  excitingCoil(fromExcitingACBD(pulses[x]));
  return steps + static_cast<int32_t>(r);
}

// Full-step drive (two phases on)
static inline int32_t fullStepDrive(int32_t steps, Rotation r) {
  constexpr static const std::array<ExcitingACBD, 4> pulses{
      ExA | ExB, // AB
      ExB | ExC, // BC
      ExC | ExD, // CD
      ExD | ExA, // DA
  };
  uint32_t x = (pulses.size() + (steps & 3)) & 3;
  excitingCoil(fromExcitingACBD(pulses[x]));
  return steps + static_cast<int32_t>(r);
}

// Half-step drive
static inline int32_t halfStepDrive(int32_t steps, Rotation r) {
  constexpr static const std::array<ExcitingACBD, 8> pulses{
      ExA,       // A
      ExA | ExB, // AB
      ExB,       // B
      ExB | ExC, // BC
      ExC,       // C
      ExC | ExD, // CD
      ExD,       // D
      ExD | ExA, // DA
  };
  uint32_t x = (pulses.size() + (steps & 7)) & 7;
  excitingCoil(fromExcitingACBD(pulses[x]));
  return steps + static_cast<int32_t>(r);
}

extern "C" void application_setup() {
  HAL_Delay(300); // time wait for LCD prepare
  shortBrake();
  //
  i2c_lcd.init();
  std::string buff(50, ' ');
  std::snprintf(buff.data(), buff.size(), u8"ステッピングモーター テスト");
  i2c_lcd.setDdramAddress(0);
  i2c_lcd.putString(buff);
  //
  HAL_TIM_Base_Start_IT(&htim2);
  TIM_OC_InitTypeDef sConfigOC = {0};
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 1000;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4);
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
}

static volatile int32_t stepCounter = 0;
static volatile Rotation rotation = Rotation::CW;

constexpr static const int32_t RightAngle = 400 / 2;

static void showPosition() {
  std::string buff(50, ' ');
  switch (rotation) {
  case Rotation::CW:
    std::snprintf(buff.data(), buff.size(), "CW %+5ld pulses", stepCounter);
    break;
  case Rotation::CCW:
    std::snprintf(buff.data(), buff.size(), "CCW %+5ld pulses", stepCounter);
    break;
  }
  i2c_lcd.setDdramAddress(0x40);
  i2c_lcd.putString(buff);
}

typedef void (*Procedure)();

static void procHomePosition() {
  i2c_lcd.setDdramAddress(0x40);
  i2c_lcd.puts(u8" HOME position. ");
  HAL_Delay(1000);
  HAL_TIM_Base_Start_IT(&htim2);
}
static void procStopPosition() {
  showPosition();
  HAL_Delay(1000);
  HAL_TIM_Base_Start_IT(&htim2);
}
static void procReturnPosition() {
  rotation = (rotation == Rotation::CW) ? Rotation::CCW : Rotation::CW;
  showPosition();
  HAL_Delay(1000);
  HAL_TIM_Base_Start_IT(&htim2);
}

static Procedure getProcedure(int32_t counter) {
  switch (std::abs(counter)) {
  case 0 * RightAngle: // home position
    return procHomePosition;
  case 1 * RightAngle: // 90
    return procStopPosition;
  case 2 * RightAngle: // 180
  case 3 * RightAngle: // 270
  case 4 * RightAngle: // 360
  case 5 * RightAngle: // 450
  case 6 * RightAngle: // 540
  case 7 * RightAngle: // 630
  case 8 * RightAngle: // 720
  case 9 * RightAngle: // 810
    return nullptr;
  case 10 * RightAngle: // 900
    return procReturnPosition;
  default:
    return nullptr;
  }
}

extern "C" void application_loop() {
  if (Procedure p = getProcedure(stepCounter); p != nullptr) {
    (*p)();
  } else {
    showPosition();
    HAL_Delay(1);
  }
}

extern "C" void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  if (htim->Instance == htim2.Instance) {
    stepCounter = halfStepDrive(stepCounter, rotation);
    if (getProcedure(stepCounter) != nullptr) {
      HAL_TIM_Base_Stop_IT(&htim2);
    }
  }
}

プロジェクトのビルド

Ctrl + B でビルド。

screenshot03

ST-Linkでプログラミング

前回と同じなので省略。

実行

狙い通りちゃんと動くし。

2022/1/2変更

Core/Src/Application.cpp
/*
 * Application.cpp
 *
 * Copyright 2021 Akihiro Yamamoto.
 * Licensed under the Apache License, Version 2.0
 * <https://spdx.org/licenses/Apache-2.0.html>
 *
 */
#include "main.h"

#include <ST7032iLcd.hpp>
#include <array>
#include <cmath>

extern I2C_HandleTypeDef hi2c1;
extern TIM_HandleTypeDef htim2;

static ST7032iLcd i2c_lcd(hi2c1);

// H-brigde pin class
template <GPIO_TypeDef *PORT(), uint32_t PIN> class HbridgePin {
public:
  static GPIO_PinState state() {
    if (((PORT()->ODR) & PIN) == 0) {
      return GPIO_PIN_RESET;
    } else {
      return GPIO_PIN_SET;
    }
  }
  static void turnOn() { HAL_GPIO_WritePin(PORT(), PIN, GPIO_PIN_SET); }
  static void turnOff() { HAL_GPIO_WritePin(PORT(), PIN, GPIO_PIN_RESET); }
};

// two H-brigdes port declaration
constexpr GPIO_TypeDef *HbHighA_GPIO_Port() { return HighA_GPIO_Port; }
constexpr GPIO_TypeDef *HbHighC_GPIO_Port() { return HighC_GPIO_Port; }
constexpr GPIO_TypeDef *HbHighB_GPIO_Port() { return HighB_GPIO_Port; }
constexpr GPIO_TypeDef *HbHighD_GPIO_Port() { return HighD_GPIO_Port; }
constexpr GPIO_TypeDef *HbLowA_GPIO_Port() { return LowA_GPIO_Port; }
constexpr GPIO_TypeDef *HbLowC_GPIO_Port() { return LowC_GPIO_Port; }
constexpr GPIO_TypeDef *HbLowB_GPIO_Port() { return LowB_GPIO_Port; }
constexpr GPIO_TypeDef *HbLowD_GPIO_Port() { return LowD_GPIO_Port; }

// two H-brigdes pins declaration
using HbPinHighA = HbridgePin<HbHighA_GPIO_Port, HighA_Pin>;
using HbPinHighC = HbridgePin<HbHighC_GPIO_Port, HighC_Pin>;
using HbPinHighB = HbridgePin<HbHighB_GPIO_Port, HighB_Pin>;
using HbPinHighD = HbridgePin<HbHighD_GPIO_Port, HighD_Pin>;
using HbPinLowA = HbridgePin<HbLowA_GPIO_Port, LowA_Pin>;
using HbPinLowC = HbridgePin<HbLowC_GPIO_Port, LowC_Pin>;
using HbPinLowB = HbridgePin<HbLowB_GPIO_Port, LowB_Pin>;
using HbPinLowD = HbridgePin<HbLowD_GPIO_Port, LowD_Pin>;

using ExcitingACBD = uint8_t;
constexpr ExcitingACBD ExA = 0b1000;
constexpr ExcitingACBD ExC = 0b0100;
constexpr ExcitingACBD ExB = 0b0010;
constexpr ExcitingACBD ExD = 0b0001;

using HiACBDLoACBD = uint8_t;
static inline HiACBDLoACBD fromExcitingACBD(ExcitingACBD acbd) {
  return (acbd & 0xf) << 4 | (acbd & 0xf);
}
//
static inline void excitingCoil(HiACBDLoACBD hiloACBD) {
  {
    ExcitingACBD hi =
        (HbPinHighA::state() ? ExA : 0) | (HbPinHighC::state() ? ExC : 0) |
        (HbPinHighB::state() ? ExB : 0) | (HbPinHighD::state() ? ExD : 0);
    ExcitingACBD lo =
        (HbPinLowA::state() ? ExA : 0) | (HbPinLowC::state() ? ExC : 0) |
        (HbPinLowB::state() ? ExB : 0) | (HbPinLowD::state() ? ExD : 0);
    if (((hi << 4) | lo) == hiloACBD) {
      return;
    }
  }
  // clang-format off
  //
  if (!(hiloACBD & (ExA << 4))) { HbPinHighA::turnOff(); }
  if (!(hiloACBD & (ExC << 4))) { HbPinHighC::turnOff(); }
  if (!(hiloACBD & (ExB << 4))) { HbPinHighB::turnOff(); }
  if (!(hiloACBD & (ExD << 4))) { HbPinHighD::turnOff(); }
  if (!(hiloACBD & ExA)) { HbPinLowA::turnOff(); }
  if (!(hiloACBD & ExC)) { HbPinLowC::turnOff(); }
  if (!(hiloACBD & ExB)) { HbPinLowB::turnOff(); }
  if (!(hiloACBD & ExD)) { HbPinLowD::turnOff(); }
  asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
  asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
  // 10
  asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
  asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
  // 20
  //
  if (hiloACBD & (ExA << 4)) { HbPinHighA::turnOn(); }
  if (hiloACBD & (ExC << 4)) { HbPinHighC::turnOn(); }
  if (hiloACBD & (ExB << 4)) { HbPinHighB::turnOn(); }
  if (hiloACBD & (ExD << 4)) { HbPinHighD::turnOn(); }
  if (hiloACBD & ExA) { HbPinLowA::turnOn(); }
  if (hiloACBD & ExC) { HbPinLowC::turnOn(); }
  if (hiloACBD & ExB) { HbPinLowB::turnOn(); }
  if (hiloACBD & ExD) { HbPinLowD::turnOn(); }
  // clang-format on
}

// short brake = turn ON all lower side switch.
static inline void shortBrake() { excitingCoil(0b00001111); }

//
enum class Rotation : int8_t { CW = -1, CCW = 1 };

// Wave drive (one phase on)
static inline int32_t waveDrive(int32_t steps, Rotation r) {
  constexpr static const std::array<ExcitingACBD, 4> pulses{
      ExA, // A
      ExB, // B
      ExC, // C
      ExD, // D
  };
  uint32_t x = (pulses.size() + (steps & 3)) & 3;
  excitingCoil(fromExcitingACBD(pulses[x]));
  return steps + static_cast<int32_t>(r);
}

// Full-step drive (two phases on)
static inline int32_t fullStepDrive(int32_t steps, Rotation r) {
  constexpr static const std::array<ExcitingACBD, 4> pulses{
      ExA | ExB, // AB
      ExB | ExC, // BC
      ExC | ExD, // CD
      ExD | ExA, // DA
  };
  uint32_t x = (pulses.size() + (steps & 3)) & 3;
  excitingCoil(fromExcitingACBD(pulses[x]));
  return steps + static_cast<int32_t>(r);
}

// Half-step drive
static inline int32_t halfStepDrive(int32_t steps, Rotation r) {
  constexpr static const std::array<ExcitingACBD, 8> pulses{
      ExA,       // A
      ExA | ExB, // AB
      ExB,       // B
      ExB | ExC, // BC
      ExC,       // C
      ExC | ExD, // CD
      ExD,       // D
      ExD | ExA, // DA
  };
  uint32_t x = (pulses.size() + (steps & 7)) & 7;
  excitingCoil(fromExcitingACBD(pulses[x]));
  return steps + static_cast<int32_t>(r);
}

extern "C" void application_setup() {
  HAL_Delay(300); // time wait for LCD prepare
  shortBrake();
  //
  i2c_lcd.init();
  std::string buff(50, ' ');
  std::snprintf(buff.data(), buff.size(), u8"ステッピングモーター テスト");
  i2c_lcd.setDdramAddress(0);
  i2c_lcd.putString(buff);
  //
  HAL_TIM_Base_Start_IT(&htim2);
  TIM_OC_InitTypeDef sConfigOC = {0};
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 1000;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4);
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
}

static volatile int32_t stepCounter = 0;
static volatile Rotation rotation = Rotation::CW;

constexpr static const int32_t RightAngle = 400 / 2;

static void showPosition() {
  std::string buff(50, ' ');
  int8_t sign = (stepCounter == 0) ? 0 : ((stepCounter < 0) ? (-1) : 1);
  switch (sign) {
  case 0:
    std::snprintf(buff.data(), buff.size(), u8" HOME position. ");
    break;
  case static_cast<int8_t>(Rotation::CW):
    std::snprintf(buff.data(), buff.size(), u8"CW %5ld pulses",
                  std::abs(stepCounter));
    break;
  case static_cast<int8_t>(Rotation::CCW):
    std::snprintf(buff.data(), buff.size(), u8"CCW %5ld pulses",
                  std::abs(stepCounter));
    break;
  default:
    break;
  }
  i2c_lcd.setDdramAddress(0x40);
  i2c_lcd.putString(buff);
}

typedef void (*Procedure)();

static void procStopPosition() {
  showPosition();
  HAL_Delay(1000);
  HAL_TIM_Base_Start_IT(&htim2);
}
static void procReturnPosition() {
  rotation = (rotation == Rotation::CW) ? Rotation::CCW : Rotation::CW;
  showPosition();
  HAL_Delay(1000);
  HAL_TIM_Base_Start_IT(&htim2);
}

static Procedure getProcedure(int32_t counter) {
  switch (std::abs(counter)) {
  case 0 * RightAngle: // home position
  case 1 * RightAngle: // 90
    return procStopPosition;
  case 2 * RightAngle: // 180
  case 3 * RightAngle: // 270
  case 4 * RightAngle: // 360
  case 5 * RightAngle: // 450
  case 6 * RightAngle: // 540
  case 7 * RightAngle: // 630
  case 8 * RightAngle: // 720
  case 9 * RightAngle: // 810
    return nullptr;
  case 10 * RightAngle: // 900
    return procReturnPosition;
  default:
    return nullptr;
  }
}

extern "C" void application_loop() {
  if (Procedure p = getProcedure(stepCounter); p != nullptr) {
    (*p)();
  } else {
    showPosition();
    HAL_Delay(1);
  }
}

extern "C" void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  if (htim->Instance == htim2.Instance) {
    stepCounter = halfStepDrive(stepCounter, rotation);
    if (getProcedure(stepCounter) != nullptr) {
      HAL_TIM_Base_Stop_IT(&htim2);
    }
  }
}

GitHubリポジトリ

https://github.com/ak1211/hello_stm32l0/tree/SteppingMotor

https://github.com/ak1211/hello_stm32l0/releases/tag/SteppingMotor