PWM

TivaCローンチパッドのTM4C123GH6PMには、16ch PWM出力がついているようですが、stellarisローンチパッドのLM4F120には、PWMモジュールがついていません。代わりにタイマをPWMモードで使うことができるので試してみます。

まず、タイマをPWMモードで使う場合、Timer0~Timer5までの6つのタイマが使用でき、さらに1つのタイマを2つの16bitタイマに分けて使うので、最大12個のPWMを生成することができます。16bit精度になりますが、8ビットのプリスケーラを設定できるため、タイマカウントとしては24bitとることができ、システムクロック上限の80MHz設定の場合でも、1/80MHz << 24= 約200ms周期まで作ることができます。

サンプルとして、RCサーボ用のPWMを生成するプログラムを作ってみました。一般的なRCサーボは、20ms周期、1ms~2msパルスのPWMを使って制御します。3秒おきにパルス幅を1.0ms->1.5ms->2.0msで可変させると、RCサーボは、0°->90°->180°で動作します。

#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_timer.h"

#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/sysctl.h"
#include "driverlib/timer.h"

void main(void) {
    unsigned long cycle,       // 周期用カウンタ
                  duty;        // パルス用カウンタ
    unsigned char cycleScale,  // 周期用プリスケーラカウンタ
                  dutyScale;   // パルス用プリスケーラカウンタ

    // システムクロック 80MHz
    SysCtlClockSet(
        SYSCTL_SYSDIV_2_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);

    // モジュール有効化
    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);

    // タイマ設定
    // Timer0を 16bitタイマ(Timer0-A、Timer0-B)として使う
    // Timer0-Aを PWMモードで使う
    TimerConfigure(TIMER0_BASE, TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_PWM);
    // Timer0-AをLow-Activeに設定
    TimerControlLevel(TIMER0_BASE, TIMER_A, true);

    // GPIO設定
    // Timer0-AをポートB6番ピンに出力
    GPIOPinConfigure(GPIO_PB6_T0CCP0);
    GPIOPinTypeTimer(GPIO_PORTB_BASE, GPIO_PIN_6);

    // PWM周期の設定
    cycle = SysCtlClockGet() / 1000 * 20;   // 20ms周期のトータルカウント値取得
    cycleScale = cycle >> 16;       // 16bitより上位ビットを
                                    // プリスケーラカウント値として取得
    cycle &= 0xFFFF;                // 16bit以下を残りのカウンタ値として取得
    TimerPrescaleSet(TIMER0_BASE, TIMER_A, cycleScale);
    TimerLoadSet(TIMER0_BASE, TIMER_A, cycle);

    // PWMパルスの設定
    duty = SysCtlClockGet() / 1000 * 1;     // 1ms周期のトータルカウント値取得
    dutyScale = duty >> 16;         // 16bitより上位ビットを
                                    // プリスケーラカウント値として取得
    duty &= 0xFFFF;                 // 16bit以下を残りのカウンタ値として取得
    TimerPrescaleMatchSet(TIMER0_BASE, TIMER_A, dutyScale);
    TimerMatchSet(TIMER0_BASE, TIMER_A, duty);

    // タイマスタート
    TimerEnable(TIMER0_BASE, TIMER_A);

    while (1) {
        SysCtlDelay(SysCtlClockGet());  // 3s待機

        duty = SysCtlClockGet() / 1000 * 1.5;   // パルス幅を1.5msに更新
        dutyScale = duty >> 16;
        duty &= 0xFFFF;
        TimerPrescaleMatchSet(TIMER0_BASE, TIMER_A, dutyScale);
        TimerMatchSet(TIMER0_BASE, TIMER_A, duty);

        SysCtlDelay(SysCtlClockGet());  // 3s待機

        duty = SysCtlClockGet() / 1000 * 2.0;   // パルス幅を2.0msに更新
        dutyScale = duty >> 16;
        duty &= 0xFFFF;
        TimerPrescaleMatchSet(TIMER0_BASE, TIMER_A, dutyScale);
        TimerMatchSet(TIMER0_BASE, TIMER_A, duty);

        SysCtlDelay(SysCtlClockGet());  // 3s待機

        duty = SysCtlClockGet() / 1000 * 1.0;   // パルス幅を1.0msに更新
        dutyScale = duty >> 16;
        duty &= 0xFFFF;
        TimerPrescaleMatchSet(TIMER0_BASE, TIMER_A, dutyScale);
        TimerMatchSet(TIMER0_BASE, TIMER_A, duty);
    }
}

RCサーボを制御する場合は、5V駆動が多いので、+3.3->+5Vレベルシフトが必要になる点は注意が必要です。