こんにちは、しゅうです。
突然ですが先日健康診断を受けました。
まだ結果は返ってきてませんが、去年から習慣化できた筋トレや食事管理がいい感じの結果に繋がっていると嬉しいです。
ということで、前回までで光センサの値を見たりモータを回すことができています。
今回はロータリエンコーダの値を見てみます。
ロータリエンコーダの仕組み
そもそも、エンコーダの仕組みはどうなっているのでしょう?
ステッピングモータを使っていた時は送ったパルス信号の数を読み取っていました。今度は、モータが一定角度回転する度にエンコーダからパルスが送られてくるので、ステッピングモータの時とは逆になります。すなわち、この送られてくるパルス信号の数を読み取ります。
簡単ですが下記図にて概略を示してみます。
詳しい説明は、こちらの記事を参照してください。
回路図
モータとNUCLEOの接続例を下記図に示します。前回の記事から新たに、エンコーダ用の電源とGNDを繋げたり、エンコーダ側のチャンネルをNUCLEOのA0とA1ピンに接続しています。このピン設定は、次の章で自動的に割り振られています。
プログラムの出力
今まではプロジェクトを分けるようにしていましたが、今回は同じMotorプロジェクトを使います。
まず最初に、エンコーダの値を拾うためのピン設定を追加します。
STM32にはエンコーダモードと呼ばれるタイマーがあるそうです。これを用いて、エンコーダから届くパルス波をカウントします。
その設定のために、今回はTimersのTIM2を使用します。ModeのところのCombined ChannelsをEncoder Modeに設定します。そのあと、ConfigurationのParameter SettingsのCounter Periodが65535、Encoder ModeがEncoder Mode TI1 and TI2になっているようにしましょう。ピン配置は自動的にPA0がTIM2_CH1、PA1がTIM2_CH2に割り振られます。これらの設定は下記画像のようになります。
今回使用しているCounter Periodの値は、カウントアップする最大値になります。この値になり次第リセットされます。下記画像にて、Counter Periodの備考欄で「この値は0から65535の間でなければならない」と書かれています。モータがx回転するごとにリセットがかかるようにすると、距離などの計算が楽そうです。
プログラムの追加
モータは適当に回転させます。前回の記事のプログラムだと連続して回転しているので、1秒回転して1秒停止する、という風にします。PWM出力を停止した直後の行にHAL_Delay関数を書き足します。その後、エンコーダの値を読み取り、表示させます。
以下でハイライトさせている部分が、今回追加したコードになります。最初に関数定義を行い、while文の中で新たにモータの停止とエンコーダの読み取り及び表示を行なっています。この時、printfを使うためにstdio.hをインクルードするのを忘れないようにしましょう。
それでは関数定義から行います。__io_putchar関数はこちらの記事で解説されています。その次に定義されているread_encoder関数はエンコーダの値を取得してきます。TIM2のCNTレジスタから値を取得し、リセットし、取得した値を返しています。TIM2の詳しい定義はstm32f103xb.hの480行目あたりに書かれています。
今回はこちらの記事を参考に書きました。
/* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ int __io_putchar(int ch) { HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 100); return ch; } int16_t read_encoder(void){ uint16_t enc_buff = TIM2->CNT; TIM2->CNT = 0; return enc_buff; } /* USER CODE END 0 */
そしてmain関数に移ります。前回から新たに追加した部分は、エンコーダの値を管理するための変数を定義し、HAL_TIM_Encoder_Start関数でエンコーダの読み取りを開始させます。そして、while文でモータを回転・停止させた後、read_encoder関数を呼び出します。返ってきた値をcount変数に足して、表示させます。
以上の動作を繰り返していきます。
/* USER CODE BEGIN 2 */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, 1); // Mode selection int16_t count=0; HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL); // Start encoder setbuf(stdout, NULL); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 1); // Rotation direction __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 500); // Sets the duty ratio, maximum value = Counter Period HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // Start PWM output printf("Motor rotating... \n\r"); HAL_Delay(1000); // Rotate for 1 second HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1); // Stop PWM output printf("Motor stopped... \n\r"); HAL_Delay(1000); // Stop for 1 second // Reading the encoder value printf("Checking encoder... \n\r"); count += read_encoder(); // Reading the encoder's value printf("Counted: %d\n\r",count); // Display count value HAL_Delay(100); // Wait a bit /* USER CODE END WHILE */
実行!
それではビルドして書き込みましょう。シリアル変換モジュールとNUCLEOボードの接続の仕方、ターミナル上での表示の仕方についてはこちらの記事を参照してください。
以下のGIFが実際に動作している様子です。
まとめ
以上でロータリエンコーダを扱うこともできました。
ここで、今回気になったところとして2点あります。
- TIMの構造体が32bitで定義されているのに対して、CNTの最大値が65535(16bit)である
- Reference Manualでは、TIM1 or TIM8と書かれていて、エンコーダを扱う時はTIM1かTIM8を使うべき?
これらに関して何か情報が分かったら、整理して公開します。
何はともあれ、モータがどのくらい回転したかを把握することができて、自作マウスがどれくらいの距離進んだかがわかります。
しかしノイズやスリップなどが影響してずれる可能性があるのでそれを補正するためにジャイロセンサも扱っていきたいと考えています。
すると、光センサ4つ・モータとエンコーダ2個ずつ・ジャイロセンサ・モード選択や実行開始用のボタンなど、同時に制御するセンサが増えてきます。
これらをうまく扱うために、割り込み処理に関することも勉強する必要があります。
特に今回のプログラムだと、エンコーダの値の取得が約2秒ごとになっているので、もっと頻繁に値を更新するためにも割り込み処理などが必要となってきます。
ということで次回からは、割り込み処理やマルチタスクなどの解説をしていこうと考えています!
ジャイロセンサは選定して手元に届いたらコード例を示して解説しようと思っています♪