STM32F103 でのエンコーダ入力取得

STM32でエンコーダがうまく取れなかったので、一度、完全にやり直して実験してみました。
その時の自分用のメモです。（chatGPT)

# STM32F103 でのエンコーダ入力取得

今回は、STM32CubeIDE 1.17 を用いて、STM32F103 における TIM3 をエンコーダ入力として利用する環境構築とテストコードの実装を行いました。以下に実施した手順とポイントをまとめます。

---

## 1. プロジェクト作成とCubeMXによるペリフェラル設定

1. **新規プロジェクト作成**  
   - STM32CubeIDE 1.17 を起動し、ターゲットとして STM32F103（例: STM32F103C8T6）を選択して新規プロジェクトを作成しました。

2. **TIM3のエンコーダモード設定**  
   - CubeMX の「Pinout」タブで、利用可能なGPIOの中から PC6 と PC7 を選択。  
   - PC6 に **TIM3_CH1**、PC7 に **TIM3_CH2** を割り当てました。  
   - STM32F103の場合、TIM3 はデフォルトでは PA6/PA7 に割り当てられていますが、**Partial Remap** を有効にすることで PC6/PC7 に変更されます。  
   - CubeIDE 1.17 では、AFIOのリマップ設定がUI上に独立して表示されないため、ピン割り当て時に自動でリマップが適用される。

3. **GPIOモードの自動設定**  
   - TIM3_CH1/CH2 を割り当てると、CubeMX は自動的に該当ピンのモードを **Alternate Function Input** に設定します。  
   - ユーザ側でモードを変更することはできませんが、エンコーダ入力としてはこの設定で問題ありません。

**設定**
タイマーをEncoder Modeにして、GPIOを割り付けたらOK。
検索ではGPIOのモードを設定する必要があるという記述がみられたけど、あまり必要ないみたい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328520/24cb20ef-cfb1-4169-b8e9-e4eb2dcafb96.png)


---

## 2. テストコードの実装

CubeMX による設定が完了したので、生成された初期化コードをもとにテスト用のコードを実装しました。主な内容は以下の通りです。

### 2.1 TIM3 のエンコーダモードの開始

CubeMX で TIM3 をエンコーダモードに設定したので、`HAL_TIM_Encoder_Start()` を呼び出してエンコーダ入力の計測を開始します。

### 2.2 カウンタ値の取得

`__HAL_TIM_GET_COUNTER(&htim3)` で TIM3 の CNT レジスタからエンコーダのカウント値を取得し、ウォッチウィンドウや UART 出力で確認します。

### サンプルコード

```c
/* USER CODE BEGIN Includes */
#include "stm32f1xx_hal.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
volatile int32_t encoder_count = 0;
/* USER CODE END PV */

int main(void)
{
  /* MCU 初期化 */
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM3_Init();  // CubeMXで設定済みのTIM3初期化

  /* USER CODE BEGIN 2 */
  // エンコーダモードで TIM3 をスタート
  if (HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL) != HAL_OK) {
    // エラー発生時の処理
    Error_Handler();
  }
  /* USER CODE END 2 */

  while (1)
  {
    /* USER CODE BEGIN WHILE */
    // タイマのカウンタ値を取得
    encoder_count = __HAL_TIM_GET_COUNTER(&htim3);
    
    // デバッグ用（例: UART 出力やウォッチウィンドウで確認）
    // printf("Encoder Count: %ld\r\n", encoder_count);
    
    HAL_Delay(100);  // 100ms 毎に更新
    /* USER CODE END WHILE */
  }
}
```

---

## 3. ビルド・デバッグエラーの対応

デバッグ実行時、以下のエラーが発生しました：

```
An internal error occurred during: "Launching stm32test".
Cannot invoke "org.eclipse.cdt.core.model.ICProject.getProject()" because the return value of "org.eclipse.cdt.debug.core.CDebugUtils.getCProject(org.eclipse.debug.core.ILaunchConfiguration)" is null
```

この問題はプロジェクトの CDT 情報に関連する不整合が原因でした。以下の対処を実施しました。

- **プロジェクトのクリーンとリビルド**  
  「Project」→「Clean…」を実行し、プロジェクトをクリーン後に再ビルド。

- **デバッグ構成の再作成**  
  「Run」→「Debug Configurations…」から既存のデバッグ設定を確認し、問題があれば削除して新規に作成。

- **ワークスペースの更新**  
  プロジェクトエクスプローラーでプロジェクトを右クリックし、「Refresh」や「Index Rebuild」を実施。

これらの対応により、無事デバッグ実行できるようになりました。

---

## 4. まとめ

今回の実装では以下のポイントを重視しました。

- **CubeMXによるペリフェラル設定**  
  - PC6/PC7 を TIM3 のエンコーダ入力として割り当て、Partial Remap を適用。
  - 自動生成された設定で GPIO モードは「Alternate Function Input」に固定。

- **テストコードの実装**  
  - `HAL_TIM_Encoder_Start()` でエンコーダ計測を開始し、`__HAL_TIM_GET_COUNTER()` でカウント値を取得。
  - ST-LINK v2 を使用してデバッグし、ウォッチウィンドウや UART で値の変化を確認。

- **デバッグエラーの対処**  
  - プロジェクトのクリーン、再ビルド、デバッグ設定の再作成、ワークスペースの更新を実施し、エラー解消。

この一連の作業により、TIM3 を用いたエンコーダ入力の取得と動作確認が無事に完了しました。今後はこの基盤をもとに、回転方向の判定や速度計算、PID制御への統合など、さらなる機能拡張を行う予定です。

---

タイマーなどの有限なリソースを大量に使うと、他の機能が制限されてしまうので、GPIOの機能のみで実施する方法を検討します。

---

# STM32F103でPC8/PC9を用いたGPIO＋割り込みによるエンコーダ回転検出

STM32F103では、ハードウェアタイマーを使ったエンコーダモードが一般的ですが、今回はタイマーを使わずにGPIOの入力と外部割り込みだけでエンコーダの回転を検出する方法を解説します。ここでは、PC8をエンコーダのA相、PC9をB相として利用する例を示します。

---

## 1. 基本方針

エンコーダはA相とB相の2路信号を出力します。GPIO＋外部割り込みのみで回転検出する方法は、以下のような構成となります。

- **A相（PC8）：**  
  - **割り込み設定**：エッジ（立ち上がりまたは立下がり）検出時に即座に処理を開始するため、外部割り込みモード（例：GPIO_MODE_IT_RISING または BOTH）に設定します。  
  - **理由:** 割り込みでA相の変化を捉えたタイミングで、B相の状態を読み取り回転方向を判定するため。

- **B相（PC9）：**  
  - **入力設定:** 通常の入力モード（GPIO_MODE_INPUT）として設定し、割り込み対象にはしません。  
  - **理由:** 割り込み処理内でB相の状態をサンプリングするだけで十分なため、割り込み負荷を低減するため。

---

## 2. CubeMXでの設定手順

### 2.1 プロジェクト作成

1. STM32CubeIDE（例：バージョン1.17）を起動し、STM32F103をターゲットに新規プロジェクトを作成します。

### 2.2 ピンの割り当て

- **PC8（A相）**  
  - モード：**External Interrupt Mode**（例：GPIO_MODE_IT_RISING または BOTH）  
  - プル設定：エンコーダの回路設計に合わせてPull-UpまたはPull-Downを設定

- **PC9（B相）**  
  - モード：**GPIO Input**  
  - プル設定：PC8と同様に設定

CubeMXのピン配置図で、PC8をクリックして「External Interrupt」モードに、PC9をクリックして「GPIO Input」に設定してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328520/37a28523-ce8c-4047-966c-61913124e8e0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328520/8edd8466-5875-4751-9128-17735f97c352.png)


### 2.3 NVIC設定

CubeMXのNVIC設定画面で、PC8に対応するEXTIライン（通常はEXTI9_5）を有効にし、適切な割り込み優先度を設定します。

---

## 3. 実装例

以下は、HALライブラリを利用したシンプルな実装例です。A相（PC8）で割り込みが発生した際に、B相（PC9）の状態を読み取り、回転方向を判定してカウンタを更新します。

```c
/* USER CODE BEGIN Includes */
#include "stm32f1xx_hal.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
volatile int32_t encoder_count = 0;
/* USER CODE END PV */

/*
 * HALライブラリでは、外部割り込みが発生するとHAL_GPIO_EXTI_Callback()が呼ばれます。
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    // PC8（A相）の割り込みが発生した場合の処理
    if (GPIO_Pin == GPIO_PIN_8) {  // PC8に割り当て
        // PC9（B相）の状態を読み出す
        GPIO_PinState b_state = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_9);
        
        // A相エッジ検出時に、B相がHIGHなら正転、LOWなら逆転と判定する例
        if (b_state == GPIO_PIN_SET) {
            encoder_count++;  // 正転：カウントアップ
        } else {
            encoder_count--;  // 逆転：カウントダウン
        }
    }
}

/*
 * main関数内の初期化例
 */
int main(void)
{
    /* MCU初期化 */
    HAL_Init();
    SystemClock_Config(); // システムクロック設定（各自実装）
    MX_GPIO_Init();       // CubeMXで生成されたGPIO初期化コード（PC8: IT, PC9: Input）
    
    // 割り込みはCubeMXで設定済みのため、mainループでencoder_countを監視するだけでOK
    while (1)
    {
        // encoder_count の値をウォッチウィンドウやUART出力で確認
        HAL_Delay(100);  // 100ms間隔でループ
    }
}
```

---

## 4. 注意点

- **割り込み負荷:**  
  高速回転や高分解能エンコーダの場合、割り込み発生頻度が非常に高くなり、CPUに負荷がかかる可能性があります。回転速度に応じて、割り込み処理をできるだけ軽量にする工夫が必要です。

- **デバウンス対策:**  
  エンコーダ信号のノイズやバウンスがある場合、ソフトウェアでタイムスタンプを利用したデバウンス処理を実装し、誤検出を防止することが重要です。

- **精度と限界:**  
  GPIOと割り込みのみでの実装はシンプルですが、ハードウェアタイマーを利用した場合に比べて、精度や信頼性が低下する可能性があるため、用途に合わせた選択が必要です。

---

## 5. まとめ

この記事では、STM32F103でPC8とPC9を用い、タイマーを使用せずにGPIO入力と外部割り込みのみでエンコーダの回転を検出する方法を紹介しました。  
- **PC8（A相）**を割り込み入力として設定し、エッジ検出時にB相の状態（PC9）を読み取ることで回転方向を判定。  
- **PC9（B相）**は通常の入力として設定し、割り込み処理でのみ状態を参照します。  

これにより、シンプルなソフトウェア実装でエンコーダの回転検出が可能になります。用途や回転速度に応じて、デバウンスや割り込み処理の最適化を行ってください。

---

ご質問や改善案があれば、コメントでお知らせください。  

Happy Coding!

---