STM32マイコン STM32L010F4P6 で
バイポーラ ステッピングモーター ST-42BYH1004 を動かした。
(配線がばらけて見栄えが悪いのでチューブに入れました。)
前回の続きになるはずが…
前回のマイコンを利用するはずが。
電池が切れたから電源装置につないだところ
電圧をよく確認していなかったからおそらく20V位がかかってレギュレータが破裂してマイコンとディスプレイモジュールを道ずれに全部破損した。
LCDをAE-AQM1602に交換
ということで秋月電子通商から新しくマイコンと
I2C接続小型キャラクタLCDモジュール(16×2行・3.3V/5V)ピッチ変換キットを購入した。
出典 : LCDデータシート(参考資料)
キットの説明書を読んでからはんだ付けしてマイコンと資料の通りにつなぐ。
AE-AQM1602 は
STM32L010F4P6マイコンを使ってI2C低電圧キャラクタ液晶モジュール(SB1602B)を動かしてみた
で使った
ストロベリーリナックス I2C低電圧キャラクタ液晶モジュール(16x2行)
と同じコントローラの ST7032i なのと, 同じI2Cアドレスなので
ブレッドボード上でピン配置を合わせて差し替えるだけで
ソフトウェアはそのまま使える。
データシートを確認する
STM32マイコン STM32L010F4P6のデータシートは毎度同じなので省略。
ST-42BYH1004のデータシートを参照する。
- 相数: 2
- 基本ステップ角: 0.9度±5%
- 1回転ステップ数: 400
- 入力定格電圧: 5V
- 定格電流: 1.0A/相
電源は1.2Vニッケル水素電池を4本直列なので, 定格5Vで丁度いいでしょう。
ステッピングモーターの基礎知識
YouTubeと
Tech Web Motor - ROHMで ステッピングモーターの基礎知識を付ける。
ステッピングモータの駆動: バイポーラ結線とユニポーラ結線
ステッピングモータ ST-42BYH1004はバイポーラ結線なので。
出典: https://techweb.rohm.co.jp/motor/knowledge/basics/basics-04/496
2相バイポーラステッピングモータの駆動 その1
出典: https://techweb.rohm.co.jp/motor/knowledge/basics/basics-04/499
2相バイポーラステッピングモータは、Hブリッジ駆動回路を2ch分使用することで駆動できます。このブロック図は、PWM動作により定電流駆動を行う回路の例で、動作は基本的にPWM出力によるDCブラシ付モータの駆動と同じです。
PWM出力によるブラシ付DCモータの駆動:Hブリッジ定電流駆動
出典: https://techweb.rohm.co.jp/motor/knowledge/basics/basics-03/362
KiCADで回路設計
ということでHブリッジ駆動回路を2組用意する。
- スイッチ素子は ダーリントントランジスタ 2SD1866 を使う
(お気に入りなんですよ。FETより安いから。) - ハイサイドドライブはブートストラップ回路とフォトカプラで絶縁
構成は簡単です。スイッチとコンデンサ、ダイオードで構成される昇圧チャージポンプで、スイッチ電圧(VIN)と内部電圧を足した電圧を上側のNch MOSFETのゲートドライブとして利用します。
出典: https://techweb.rohm.co.jp/knowledge/dcdc/dcdc_sr/dcdc_sr01/829
回路定数は詰める必要がある。(そこにあった部品を使っただけ)
ブレッドボードで配線する
完成写真。
オシロスコープで見ているとノイズが邪魔だったので, そこにあったフェライトコアを通した。
上がパワー段, 下が制御段。
パワー段の拡大。
STM32CubeIDEでプロジェクトを開く
前回のプロジェクトを再利用する。
- CubeIDE Version: 1.8.0
- C++17
STM32L010F4P6の設定
クロックは24MHz
ポートAをOutputに
2相バイポーラステッピングモーターを動かしてみる
とりあえず
ステッピングモーターをホーム位置から
- 90度位置まで時計回り(CW)に移動して1秒停止
- 900度位置まで時計回り(CW)に移動して1秒停止
- 90度位置まで反時計回り(CCW)に移動して1秒停止
- ホーム位置に戻って1秒停止
- -90度位置まで反時計回り(CCW)に移動して1秒停止
- -900度位置まで反時計回り(CCW)に移動して1秒停止
- -90度位置まで時計回り(CW)に移動して1秒停止
- ホーム位置に戻って1秒停止 を繰り返すプログラムを書いた。
パルス速度はタイマ割り込みで制御。
割り込みハンドラで計算しているので, パルス速度を上げるにはクロック周波数を上げないと追いつかないかも。
1-2相励磁(halfStepDrive)なので, 400stepで半周動く。
/*
* 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 でビルド。
ST-Linkでプログラミング
前回と同じなので省略。
実行
狙い通りちゃんと動くし。
https://www.instagram.com/reel/CXq6JkflKPL/?utm_source=ig_web_copy_link
2022/1/2変更
/*
* 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