Site icon rpine lab Tech Blog

趣味のプログラミング(Web系・バックエンド)や自宅ラボ(Homelab)構築・マイコンを使った電子工作などを雑多に扱った技術ブログです。

CH32V003マイコンでRCサーボモーターを動かす(PWM出力)

はじめに

CH32V003マイコンのタイマーのPWM出力を使ってRCサーボモーターを制御してみます。

環境

  • RCサーボモーター:MG90D(デジタルサーボ)
  • マイコン:CH32V003F4P6(SSOP20ピン)
  • IDE:MounRiver Studio Ⅱ(VSCodeベースの公式開発環境)
  • SDK:公式SDK(MRS2に同梱)

配線

マイコンのPD2ピン(TIM1チャンネル1出力)にサーボモーターの信号線を接続します。

サーボ信号線の接続:

  • 黄色(信号線)→PD2(19番ピン)
  • 赤色(電源)→5V
  • 茶色(GND)→GND
CH32V003F4P6ピン配置(CH32V003データシートより)
CH32V003F4P6ピン配置(CH32V003データシートより)

タイマーの設定

サーボモーターを制御するために必要な信号は、タイマー1(TIM1)のPWM出力を使用して生成します。

  • カウント周波数:1 MHz
    • マイクロ秒(us)単位でパルス幅を設定可能にするため
    • マイコンの動作周波数(48 MHz)をプリスケーラー(分周器)で1/48に分周することで生成(実際の分周比はマイコンの動作周波数がセットされるグローバル変数SystemCoreClockを使って動的に計算)
  • 周期:20 ms(20,000カウント)
    • PWM信号の送信周期を50 Hzに

プログラム

まず、MounRiver Studio Ⅱでプロジェクトを作成します。(プロジェクト作成・設定方法は前記事にて解説)

再利用しやすいようにサーボ制御関数はmain.cとは別ファイルに切り出します。Userフォルダに次の2ファイル(servo.h, servo.c)を追加します。

#ifndef __SERVO_H
#define __SERVO_H

#include "ch32v00x_conf.h"

#ifdef __cplusplus
 extern "C" {
#endif

void Servo_Init(void);
void Servo_SetPulse(uint16_t us);

#ifdef __cplusplus
}
#endif

#endif /* __SERVO_H */
User/servo.h
#include "servo.h"

/* サーボ初期化 */
void Servo_Init(void) {
    /* GPIOとTIM1にクロック供給 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_TIM1, ENABLE);

    /* PD2ピンをAF(Alternate Function=各種ペリフェラルによる出力)モードで初期化 */
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    /* TIM1の設定 */
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
    TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStruct.TIM_Prescaler = SystemCoreClock / 1000000 - 1; /* 分周比(-1に注意) */
    TIM_TimeBaseInitStruct.TIM_Period = 20000; /* 周期 */
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);

    /* TIM1出力チャンネル1の設定 */
    TIM_OCInitTypeDef TIM_OCInitStruct;
    TIM_OCStructInit(&TIM_OCInitStruct);
    TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; /* 出力モード */
    TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; /* 出力有効 */
    TIM_OCInitStruct.TIM_Pulse = 0; /* 初期パルス幅 */
    TIM_OC1Init(TIM1, &TIM_OCInitStruct);

    /* TIM1の有効化 */
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    TIM_Cmd(TIM1, ENABLE);
}

/* サーボ信号のパルス幅を設定(単位:us) */
void Servo_SetPulse(uint16_t us) {
    TIM_SetCompare1(TIM1, us);
}
User/servo.c

動作確認

テストプログラムを作成します。

#include "debug.h"
#include "servo.h"

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
#if (SDI_PRINT == SDI_PR_OPEN)
    SDI_Printf_Enable();
#else
    USART_Printf_Init(115200);
#endif

    Servo_Init();

    Servo_SetPulse(1500); /* 中心 */
    Delay_Ms(1000);
    Servo_SetPulse(1000); /* 逆方向の最大(≒-45度) */
    Delay_Ms(1000);
    Servo_SetPulse(2000); /* 順方向の最大(≒+45度) */
    Delay_Ms(1000);
    Servo_SetPulse(1500); /* 中心 */
    Delay_Ms(2000);

    while (1) {
        for (uint16_t i = 1000; i <= 2000; i+=100) {
            Servo_SetPulse(i);
            printf("Pulse: %d\n", i);
            // Delay_Ms(500);
        }
    }
}
main.c

最初に中心→-45度→+45度→中心と動かした後、現在地をprintfで出力しながら一方向に首振りをするプログラムになっています。

デバッグ機能を使わない場合はprintfをDelay_Ms関数に置き換える必要があります。

これを動作させた様子(前半のみ)が次のGIFです。

サーボモーター制御の様子
サーボモーター制御の様子

出力したPWM信号をオシロに入力して確認した様子を以下に示します。オシロの下の表示を見ると、きちんと50 Hz(20 ms)間隔で1.5msのパルス幅の信号が出力されていることが分かります。使っているオシロはAlientek DS100ミニデジタルオシロスコープで、こんな感じのマイコンの出力信号であれば手軽にデバッグできます。

中心=1500us出力時の様子
中心=1500us出力時の様子

まとめ

CH32V003マイコンを使って公式SDKでPWM出力を生成し、RCサーボモーターを制御してみました。

他の機能も色々試していきたいと思います。

参考サイト

開発ボードのドキュメントと共にサンプルコードが機能ごとに色々入っているので、公式SDKの使い方が分からなかったらこれを見るのが一番手っ取り早い。PWM出力のコードはEVT/EXAM/TIM/PWM_Output/User/main.cを参考にした。