やっとArduino UNO Qで、ハードウエアエンコーダ(AB相)の計測ができるようになった。TIM2を用いることで、32bitカウンタが利用できる。これはでかい。ESP32にはハードウエアABカウンタがない(らしい)ので、STM32U585の得点になりますね。
一応、記念に残しておきます。どなたかのために。
#Arduino #UNO #Q #encoder #TIM2
[stm32_enc_tim2.ino]
/*
TIM2でAB相エンコーダのカウント成功(2026/01/28)
TIM2_CH1 : D15(PA5)
TIM2_CH2 : D02(PB3)
*/
#include "stm32_enc.h"
#include <stm32u5xx_ll_tim.h>
#define XSERIAL Serial1 // PDF資料に基づきUART1を使用
#define ENC_A 15 // D15
#define ENC_B 2 // D2
unsigned long debug_interval = 0;
STM32Encoder enc(1.0); // 係数 1.0 で初期化
void setup() {
XSERIAL.begin(230400);
enc.begin();
enc.reset();
XSERIAL.println("TIM2 Encoder (D15/D2) Ready.");
}
void loop() {
if (millis() - debug_interval >= 500) {
debug_interval = millis();
// TIM2の値を直接、およびクラス経由で表示
uint32_t raw_reg = LL_TIM_GetCounter(TIM2);
XSERIAL.print("Reg:");
XSERIAL.print(raw_reg);
XSERIAL.print(" | Count:");
XSERIAL.print(enc.getCount());
XSERIAL.print(" | Phys:");
XSERIAL.println(enc.getPhysicalValue());
}
if (XSERIAL.available() > 0) {
String input = XSERIAL.readStringUntil('\n');
input.trim();
if (input == "R") {
enc.reset();
XSERIAL.println("Reset Done.");
}
}
}
[stm32_enc.h]
#ifndef STM32_ENC_H
#define STM32_ENC_H
#pragma once
#include <stdint.h>
class STM32Encoder {
public:
// コンストラクタ: 物理変換係数を設定 (デフォルト 1.0)
STM32Encoder(double factor = 1.0);
void begin();
void reset();
// 生のカウント値を返す (TIM2は32bit)
int32_t getCount();
// (getCount / 4.0) * factor を返す
double getPhysicalValue();
// 係数を後から変更する
void setFactor(double factor);
private:
double _factor;
};
#endif
[stm32_enc.cpp]
#include "stm32_enc.h"
#include <Arduino.h>
#include <stm32u5xx_ll_tim.h>
#include <stm32u5xx_ll_bus.h>
#include <stm32u5xx_ll_gpio.h>
STM32Encoder::STM32Encoder(double factor) : _factor(factor) {}
void STM32Encoder::begin() {
// 1. クロック有効化 (TIM2, Port A, Port B)
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA);
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB);
// 2. D15 (PA5) -> TIM2_CH1 (AF1)
LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_5, LL_GPIO_MODE_ALTERNATE);
LL_GPIO_SetAFPin_0_7(GPIOA, LL_GPIO_PIN_5, LL_GPIO_AF_1);
LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_5, LL_GPIO_PULL_UP);
LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_5, LL_GPIO_SPEED_FREQ_VERY_HIGH);
// 3. D2 (PB3) -> TIM2_CH2 (AF1)
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_3, LL_GPIO_MODE_ALTERNATE);
LL_GPIO_SetAFPin_0_7(GPIOB, LL_GPIO_PIN_3, LL_GPIO_AF_1);
LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_3, LL_GPIO_PULL_UP);
LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_3, LL_GPIO_SPEED_FREQ_VERY_HIGH);
// 4. TIM2 設定
LL_TIM_DisableCounter(TIM2);
LL_TIM_SetPrescaler(TIM2, 0);
LL_TIM_SetEncoderMode(TIM2, LL_TIM_ENCODERMODE_X4_TI12);
// 入力設定 (正転/逆転は以前の知見に基づき、物理配線に合わせて調整してください)
LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI);
LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING);
LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1);
LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI);
LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING);
LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV1);
// 32bit対応 (最大値 0xFFFFFFFF)
LL_TIM_SetAutoReload(TIM2, 0xFFFFFFFF);
LL_TIM_SetCounter(TIM2, 0);
LL_TIM_EnableCounter(TIM2);
}
int32_t STM32Encoder::getCount() {
// 32bitレジスタをそのまま符号付き32bitとして取得
return (int32_t)LL_TIM_GetCounter(TIM2);
}
double STM32Encoder::getPhysicalValue() {
return ((double)getCount() / 4.0) * _factor;
}
void STM32Encoder::setFactor(double factor) {
_factor = factor;
}
void STM32Encoder::reset() {
LL_TIM_SetCounter(TIM2, 0);
}