Q&A (電気電子)

【PIC】PICマイコンのPWM機能を使ってRC用サーボを制御する方法を紹介する

今回はPICマイコンのPWM機能を利用し、ラジコン用サーボの角度制御を行ってみました。

簡単なサンプルプログラムも紹介してみます。

今回やること

今回やることは下記です。

PIC16F1827のRA3ポートからPWM信号を出力し、RC用サーボの角度を制御する。

構成図は下記の通り。

サンプルプログラムの紹介

初めにサンプルプログラムを紹介します。

MPLABでプロジェクトを作成、main.cを作成後、
下記のプログラムをコピー&ペーストしてコンパイルすれば、while(1)の関数内で指定した角度でサーボモータを制御できます。

/* PIC16F1827 Configuration Bit Settings */
/* 'C' source line config statements */
/* CONFIG1 */
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)
/* CONFIG2 */
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF       // PLL Enable (4x PLL enabled)
#pragma config STVREN = OFF      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)
/* #pragma config statements should precede project file includes. */
/* Use project enums instead of #define for ON and OFF. */
/*==========================================================================================================================================*/
/*
* File:   main.c
* Author: ICE_MEGANE
*
* Created on 2021/04/2, 22:26
*/
#include <xc.h>
typedef  unsigned char   UC;
typedef  unsigned short  US;
typedef  unsigned long   UL;
#define _XTAL_FREQ  16000000     /* 16MHz */
#define     CLEAR   0
#define     SET     1
/* PIC16シリーズは割り算ができない(超遅い)ためあらかじめ角度に相当するパルス幅をROM定義しておく */
#define SERVO_PWM_CNT_MAX           (US)600U        /* Ton_servo/((1/Tosc)*prescaler) = 0.0024/((1/16MHz)*64) */
#define SERVO_PWM_CNT_MIN           (US)125U        /* Ton_servo/((1/Tosc)*prescaler) = 0.0005/((1/16MHz)*64) */
#define SERVO_ANGLE_MAX             (US)180U
#define SERVO_ANGLE_MIN             (US)0U
#define SERVO_ANGLE_DEFI___0DEG      (US)((((UL)0 )*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI___5DEG      (US)((((UL)5 )*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__10DEG      (US)((((UL)10)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__15DEG      (US)((((UL)15)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__20DEG      (US)((((UL)20)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__25DEG      (US)((((UL)25)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__30DEG      (US)((((UL)30)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__35DEG      (US)((((UL)35)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__40DEG      (US)((((UL)40)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__45DEG      (US)((((UL)45)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__50DEG      (US)((((UL)50)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__55DEG      (US)((((UL)55)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__60DEG      (US)((((UL)60)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__65DEG      (US)((((UL)65)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__70DEG      (US)((((UL)70)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__75DEG      (US)((((UL)75)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__80DEG      (US)((((UL)80)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__85DEG      (US)((((UL)85)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__90DEG      (US)((((UL)90)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__95DEG      (US)((((UL)95)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__100DEG     (US)((((UL)100)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__105DEG     (US)((((UL)105)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__110DEG     (US)((((UL)110)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__115DEG     (US)((((UL)115)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__120DEG     (US)((((UL)120)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__125DEG     (US)((((UL)125)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__130DEG     (US)((((UL)130)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__135DEG     (US)((((UL)135)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__140DEG     (US)((((UL)140)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__145DEG     (US)((((UL)145)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__150DEG     (US)((((UL)150)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__155DEG     (US)((((UL)155)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__160DEG     (US)((((UL)160)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__165DEG     (US)((((UL)165)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__170DEG     (US)((((UL)170)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__175DEG     (US)((((UL)175)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
#define SERVO_ANGLE_DEFI__180DEG     (US)((((UL)180)*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
/* -> 1degree ~= 2 : 0DEG = 125, 1DEG = 127, ... */
#define SERVO_DEG___0               (UC)0U
#define SERVO_DEG___5               (UC)1U
#define SERVO_DEG__10               (UC)2U
#define SERVO_DEG__15               (UC)3U
#define SERVO_DEG__20               (UC)4U
#define SERVO_DEG__25               (UC)5U
#define SERVO_DEG__30               (UC)6U
#define SERVO_DEG__35               (UC)7U
#define SERVO_DEG__40               (UC)8U
#define SERVO_DEG__45               (UC)9U
#define SERVO_DEG__50               (UC)10U
#define SERVO_DEG__55               (UC)11U
#define SERVO_DEG__60               (UC)12U
#define SERVO_DEG__65               (UC)13U
#define SERVO_DEG__70               (UC)14U
#define SERVO_DEG__75               (UC)15U
#define SERVO_DEG__80               (UC)16U
#define SERVO_DEG__85               (UC)17U
#define SERVO_DEG__90               (UC)18U
#define SERVO_DEG__95               (UC)19U
#define SERVO_DEG__100              (UC)20U
#define SERVO_DEG__105              (UC)21U
#define SERVO_DEG__110              (UC)22U
#define SERVO_DEG__115              (UC)23U
#define SERVO_DEG__120              (UC)24U
#define SERVO_DEG__125              (UC)25U
#define SERVO_DEG__130              (UC)26U
#define SERVO_DEG__135              (UC)27U
#define SERVO_DEG__140              (UC)28U
#define SERVO_DEG__145              (UC)29U
#define SERVO_DEG__150              (UC)30U
#define SERVO_DEG__155              (UC)31U
#define SERVO_DEG__160              (UC)32U
#define SERVO_DEG__165              (UC)33U
#define SERVO_DEG__170              (UC)34U
#define SERVO_DEG__175              (UC)35U
#define SERVO_DEG__180              (UC)36U
#define SERVO_DEG__MAX              SERVO_DEG__180
#define SERVO_ANGLE_DEFI_NUM        (UC)SERVO_DEG__MAX + (UC)1U
static US us_servo_angle_convet_array[SERVO_ANGLE_DEFI_NUM] = 
{
SERVO_ANGLE_DEFI___0DEG,
SERVO_ANGLE_DEFI___5DEG,
SERVO_ANGLE_DEFI__10DEG,
SERVO_ANGLE_DEFI__15DEG,
SERVO_ANGLE_DEFI__20DEG,
SERVO_ANGLE_DEFI__25DEG,
SERVO_ANGLE_DEFI__30DEG,
SERVO_ANGLE_DEFI__35DEG,
SERVO_ANGLE_DEFI__40DEG,
SERVO_ANGLE_DEFI__45DEG,
SERVO_ANGLE_DEFI__50DEG,
SERVO_ANGLE_DEFI__55DEG,
SERVO_ANGLE_DEFI__60DEG,
SERVO_ANGLE_DEFI__65DEG,
SERVO_ANGLE_DEFI__70DEG,
SERVO_ANGLE_DEFI__75DEG,
SERVO_ANGLE_DEFI__80DEG,
SERVO_ANGLE_DEFI__85DEG,
SERVO_ANGLE_DEFI__90DEG,
SERVO_ANGLE_DEFI__95DEG,
SERVO_ANGLE_DEFI__100DEG,
SERVO_ANGLE_DEFI__105DEG,
SERVO_ANGLE_DEFI__110DEG,
SERVO_ANGLE_DEFI__115DEG,
SERVO_ANGLE_DEFI__120DEG,
SERVO_ANGLE_DEFI__125DEG,
SERVO_ANGLE_DEFI__130DEG,
SERVO_ANGLE_DEFI__135DEG,
SERVO_ANGLE_DEFI__140DEG,
SERVO_ANGLE_DEFI__145DEG,
SERVO_ANGLE_DEFI__150DEG,
SERVO_ANGLE_DEFI__155DEG,
SERVO_ANGLE_DEFI__160DEG,
SERVO_ANGLE_DEFI__165DEG,
SERVO_ANGLE_DEFI__170DEG,
SERVO_ANGLE_DEFI__175DEG,
SERVO_ANGLE_DEFI__180DEG
};
void main(void );
static void register_setup( void );
static void rs_servo_angle_set( UC us_angle_defi );
static void rs_ccp3_dutyset( US us_duty );
/**************************************************************/
/*  Function:                                                 */
/*  main task                                                 */
/*                                                            */
/**************************************************************/
void main(void)
{   
register_setup();
while(1)
{
rs_servo_angle_set(SERVO_DEG__180);
}
}
/**************************************************************/
/*  Function:                                                 */
/*  register setup                                            */
/*                                                            */
/**************************************************************/
static void register_setup( void )
{
/* Oscillator setuo */
OSCCON =  0x7A;         /* |1|1|1|1|-|1|#|0|0| */       /* 16MHz : (0x70 and Fosc 4xPLLoff) or (0xF2 !<-Fosc anaffected)  */                 
OSCTUNE =  0x00;        /* |#|#|0|0|-|0|0|0|0| */
/* Option register setup */
//OPTION_REG =  0xC8;     /* |1|1|0|0|-|0|1|1|1| */
/* Alternate pin function setup */
APFCON0 =  0x08;        /* |0|0|0|0|-|1|0|0|0| */
APFCON1 =  0x00;        /* |x|x|x|x|-|x|x|x|0| */
/* Interrupt setup */
//    INTCON =  0x20;         /* |0|0|1|0|-|0|0|0|0| */
//    PIE3 = 0x00;            /* |x|x|1|1|-|1|x|1|x| */
/* Port setup */
TRISA = 0x00;           /* |0|0|0|0|-|0|0|0|0| */
/* CCP setup */
CCPTMRS = 0x93;         /* |1|0|0|1|-|0|0|1|1| */
CCP3CON = 0x0C;         /* |0|0|0|0|-|1|1|0|0| */
CCPR3L = 0x00;          /* |0|0|0|0|-|0|0|0|0| */
PIR3 &= 0xEF;           /* |x|x|x|0|-|x|x|x|x| */
/* Timer4 setup (Timer4 assigned to CCP3) */
TMR4 = 0xFF;
T4CON = 0x07;           /* |x|0|0|0|-|0|1|1|1| */
PR4 = 0xFF;             /* Timer4 Overflow val (PWM period) setup : 8bit MSB */
}
/**************************************************************/
/*  Function:                                                 */
/*  main task                                                 */
/*  Set Servo motor angle                                     */
/**************************************************************/
static void rs_servo_angle_set( UC us_angle_defi )
{
US us_angle_cnt;
if( us_angle_defi >= SERVO_ANGLE_DEFI_NUM )
{
us_angle_defi = SERVO_ANGLE_DEFI_NUM - (UC)1U;
}
us_angle_cnt = us_servo_angle_convet_array[us_angle_defi];
rs_ccp3_dutyset( us_angle_cnt );
}
/**************************************************************/
/*  Function:                                                 */
/*  main task                                                 */
/*  Set CCP3 module duty                                      */
/**************************************************************/
static void rs_ccp3_dutyset( US us_duty )
{
CCPR3L = (UC)( us_duty >> 2 );     /* 0b0011-1111-1111 -> 0b0000-1111-1111-(11) */
us_duty &= 0x0003;
us_duty = us_duty << 4;
CCP3CON &= 0xCF;                    /* DCxB Clear */
CCP3CON |= (UC)( us_duty );         /* 0b0000-0000-0011 -> 0b0000-0011-0000 */
}
/**************************************************************/
/*  Function:                                                 */
/*  main task                                                 */
/*  Make 1ms time base!                                       */
/**************************************************************/

MPLABにおけるプロジェクトのつくり方、書き込み方法は下記の記事にまとめてみました。必要であれば参考にしてみてください。

【PIC】開発環境整備:MPLABで作成したプログラムをPICKIT3で書き込む方法を紹介する

あまり新しい情報がなかったのでまとめてみました。 開発環境(ソフト)->開発環境(ハード)->プロジェクトの作成->実際に書き込みの順で書いてあります。 多少参考になるかもしれん。 ...

続きを見る

ラジコン用サーボの角度はPWM信号のパルス幅で制御できる

以降ではプログラムの内容と合わせて説明をしてみます。

まずはラジコンなどで使用されるサーボモータの概要から説明してみます。

サーボモータの概要

サーボモータは、パルス入力により角度を制御することができる駆動部品になります。

だいたい下記のような箱型の形をしています。

標準的なサーボでは、3つの線がでており、それぞれ下記のような役割があります。

サーボモータの角度範囲はモノによって異なりますが、180度、270度のものが一般的です。

サーボの角度制御に必要なPWM波形の仕様を確認する

サーボモータの角度を制御するためには、PWM入力ポートへPWM信号を入力する必要があります。

サーボモータが受け付けるPWM入力信号の仕様は各メーカーが出すデータシートに記載があります。

今回はTowerProのメジャーなサーボである「SG90」を例に紹介してみます。

SG90のパルス入力に対する角度範囲を調べてみると、下記のような説明が見つかりました。

この説明からわかることを言葉にしてみると下記のようになります。

・PWMのキャリア周期は20ms
・パルスの電圧はサーボの電源と同じで、4.8V~5.0Vの範囲に設定する
・角度を制御するON-dutyは0.5ms~2.4msの間で入力する

補足のコメントを見てみると、1.45msのON-dutyでパルスが入力された時をサーボの中立として、±0.95msの変化で前後90度の角度調節ができると記載があります。

仕様としてはこれだけになります。

一点だけ留意すべき点があるのですが、キャリア周期についてはそれほど正確さが求められません。

データシートには20msにするようにと記載があるのですが、実はON-dutyのパルス幅を守っていればOFF-dutyは少し違っても大丈夫です。

例を挙げてみると、下記の2つのパルスはキャリア周期、dutyがともに異なるのですが、ON区間の時間の長さという意味では同じです。
このパルスをサーボモータに入力した場合、どちらのパルスにおいても角度は90度になります。

サーボモータ側の基板のマイコンがinputキャプチャでon時間しか見ていないっぽいです。

OFF時間をあまりにも短くしすぎるとバグりそうな気がしますが、ある程度は問題なさそうです。

PICマイコン側のPWM出力の仕様を決める

上記で説明したサーボが受け付けるパルスをもとに、PICマイコンから出すPWMの仕様を決めていきます。

今回はPIC16F1827を使用するのですが、PWMに使えるタイマはtimer2, timer4, timer6 のみなので、8bitの分解能しかありません。

キャリア周期が長くい状態でdutyが小さい範囲で制御するよりも、キャリア周期がなるべくON-dutyの時間に近い方が、角度操作の分解能が細かくなります。

よって今回は下記のような設定にしてみました。

この条件における角度の分解能を計算してみると、0~180度を600-125分割で制御することになるため、下記のようになります。

まぁ、悪くはない精度かなと思います。

上記の分解能はPIC16F1827のクロックを16MHzとした場合における、設定可能な上限値になります。

一つ上のクロックは32MHzですが、これだとタイマのカウントクロックが2倍になります。
これだと488Hzのキャリア周波数となり、dutyを100%近くにしてもON時間が2ms程度になってしまいます。
この場合、制御可能なサーボの角度範囲がが0~135度ぐらいに狭くなってしまいます。

これ以上クロックを下げた場合は逆に1カウントで制御可能な角度の分解能が下がります。

また内蔵発振器の周波数を下げるということは、MCUそのものの計算速度を下げるということなので、全体的に処理が遅くなります。

タイマのプリスケーラが64以上あればなぁ~、、、と思わずにはいられないですが、ミッドレンジ製品なので致し方なし!

プログラムの説明

PWMの各種設定については下記の記事で説明しているので、気になる方は参考にしてみて下さい。

PWMの設定
【PIC】PICマイコンのPWMの使い方、設定方法(CCP-PWM機能)を紹介する

今回はPIC16F1827について、CCPモジュールを使ってPWM出力を行ってみます。 PICのPWM機能は若干癖がある マイコンの一般的な機能であるPWMについて、今回はPICマイコンでテストしてみ ...

続きを見る

ここでは今回作成したプログラムについて手短に説明してみたいと思います。

サーボの角度定義式をマクロ定義

今回はサーボの角度定義をマクロ定義を使って計算し、ROM上に数値を配置しました。

PIC16F1827ですが、基本的に割り算を効率よく処理するユニットを搭載していません。
そのため、プログラム中に「A/B」のような記述をすると処理が激重になります。

よって今回は配列に数値をあらかじめ保存しておき、20度=配列[4] といった形で数値取得を行うようなプログラム構成としました。

それぞれの角度に対するdutyですが、PWMの周波数を変えたり制御範囲を変えたりする際にすべての要素を変えるのが面倒なので、下記のように数式にして定義してみました。

#define SERVO_PWM_CNT_MAX           (US)600U        /* Ton_servo/((1/Tosc)*prescaler) = 0.0024/((1/16MHz)*64) */
#define SERVO_PWM_CNT_MIN           (US)125U        /* Ton_servo/((1/Tosc)*prescaler) = 0.0005/((1/16MHz)*64) */
#define SERVO_ANGLE_MAX             (US)180U
#define SERVO_ANGLE_MIN             (US)0U
#define SERVO_ANGLE_DEFI___0DEG      (US)((((UL)0 )*( (UL)SERVO_PWM_CNT_MAX - (UL)SERVO_PWM_CNT_MIN )) / ( (UL)SERVO_ANGLE_MAX ) + (UL)SERVO_PWM_CNT_MIN)
・・・

マクロ定義だと読みにくいので、数式っぽい表記に直してみると下記のようになります。

今回の設定値で考えると下記のようになります。

軽く調べてみた感じ、サーボ界において、「パルス入力仕様はキャリア周期20ms、ON時間 0.5ms~2.5msぐらい」というのは共通認識らしく、ほかのメーカのサーボも似たような仕様でした。

あまり変えることはないかもしれませんが、一応数式で用意してみました。

サーボ用PWM dutyを保存した配列の用意

上記で用意した角度の定義は配列に登録し、インデックスで呼び出せるようにしました。

今回は0~180度を5度刻みで37個の要素を用意しました。

ここでやりたくなるのが配列の要素を大量に用意してより細かい角度でサーボを制御する!ということなのですが、注意点があります。

PIC16F1827はRAMが384byteしかありません。
またその384byteもまとまって存在するわけではなく、複数のブロックに分けて配置されています。
このため、これらのRAMをまたいで使うようなことをするとコンパイルエラーになる場合があります。
(実際に要素数255とかにするとエラーになります。)

そのような場合は、なるべくROM上に配列を配置することでエラーの回避ができます。

今回のようにサーボの角度に対するPWMのカウント数の定義は、基本的にプログラム動作中に変えない固定値になります。

よって値を書き換えられるRAMに配置しておく必要はないため、ROMに配置します。

プログラムをROMに配置するには、「const」をつけて配列を宣言するだけです。

例えば今回の場合、下記のように配列をramに定義していると、メモリの使用内容は以下のようになります。

これを「const」宣言をつけてコンパイルしなおすと、下記のように変わります。

もともとメモリが少ないため、配列のようなたくさんメモリを使う変数がなくなると、効果絶大です。
22%から3%へ減りました。

対するROMは5%のままほぼ変わっていません。
プログラムメモリの容量には意外と余裕があるので、ramに配置する必要がないのならなるべくconst宣言した方がいい気がします。

メモリが知らんうちに100%超えていると勝手に重複して書き換えられたりする場合もあるので、RAM容量が少ないマイコンを使う場合はちょっと注意が必要です・・・

サーボ角度の設定用関数

続いてサーボモータの角度設定用関数です。

配列のインデックスを引数として、関数内でインデックスに対応したdutyを配列から取得し、CCP3のduty設定関数に渡しているだけです。

特に説明は不要かなと思います。

static void rs_servo_angle_set( UC us_angle_defi )
{
US us_angle_cnt;
if( us_angle_defi >= SERVO_ANGLE_DEFI_NUM )
{
us_angle_defi = SERVO_ANGLE_DEFI_NUM - (UC)1U;
}
us_angle_cnt = us_servo_angle_convet_array[us_angle_defi];
rs_ccp3_dutyset( us_angle_cnt );
}

duty出力関数

dutyの設定については、CCPRxLレジスタと、CCPxCONレジスタのDC1B、DC0Bビットに値を設定するだけです。
PWMを出力するプログラムを紹介した際に説明したため、詳細は割愛します。

static void rs_ccp3_dutyset( US us_duty )
{
CCPR3L = (UC)( us_duty >> 2 );     /* 0b0011-1111-1111 -> 0b0000-1111-1111-(11) */
us_duty &= 0x0003;
us_duty = us_duty << 4;
CCP3CON &= 0xCF;                    /* DCxB Clear */
CCP3CON |= (UC)( us_duty );         /* 0b0000-0000-0011 -> 0b0000-0011-0000 */
}

まとめ

今回はPICマイコンでラジコン用サーボを制御して遊んでみました。

あまりサーボについては詳しくなかったのですが、ON時間だけで制御できるのは少し驚きました。

ラジコン用サーボは割となんにでも使えるので、自由にカスタマイズして使ってもらえればな~と思います。

また何かの制御プログラムを考えたらサンプル作って公開してみます。

それでは、また。

-Q&A (電気電子)
-, , , ,