【NanoPi NEO/Armbian】PWMでLチカならぬLフワ?

シングルボードコンピュータ(SBC)

PWMを使用可能にするには

前回はGPIO制御でLEDの点灯・消灯を

今回はハードウェアPWM制御でLEDの明るさを変えてみましょう。

NanoPi NEOは1チャネルのPWM(PWM0)を搭載していますが、ArmbianのデフォルトではPWM機能とピンを共有(Multiplex)しているUART0がシリアルコンソール用途のために有効になっており、PWM機能は使用できない状態になっています。まず最初にDevice Tree overlaysという仕組みでUART0機能を無効・PWM機能を有効にします。ここでは詳しい説明は割愛し、PWMを有効にする方法だけ記載します。

まず、ピンの状態を確認します。PWM0はPA5ピンにアサインされています(Allwinner H3のデータシートで確認可能です)。

# su
パスワード:
# cat /sys/kernel/debug/pinctrl/1c20800.pinctrl/pinmux-pins
:(省略)
pin 4 (PA4): 1c28000.serial (GPIO UNCLAIMED) function uart0 group PA4
pin 5 (PA5): 1c28000.serial (GPIO UNCLAIMED) function uart0 group PA5
:(省略)

前述の通りPA5はUART0にアサインされている状態です。これをPWM0に切り替えます。具体的には/boot/armbianEnv.txtを開き内容を一部変更します。

「armbianEnv.txt」変更点①

「console=serial」の行の先頭に「#」を追加してコメントアウトする。

「armbianEnv.txt」変更点②

「overlays」の行に「pwm」と追記する。

以下のような感じです。

$ sudo vi /boot/armbianEnv.txt
verbosity=1
bootlogo=false
#console=serial
disp_mode=1920x1080p60
overlay_prefix=sun8i-h3
overlays=usbhost1 usbhost2 pwm
rootdev=UUID=4898d57e-5d6d-4c9c-8e8a-96f3009f5fb5
rootfstype=ext4
usbstoragequirks=0x2537:0x1066:u,0x2537:0x1068:u

※上記設定を行ってもUSB接続でのコンソール機能は利用可能です。

尚、本設定を有効にするためにはNanoPi NEOを再起動する必要があります。

Device Tree overlaysに関してもっと詳しく知りたい方は以下を参照ください。

Device Tree overlays - Armbian Documentation

PWM機能が有効になったことを確認

再起動したらPWM機能が有効になっていることを確認します。以下の通りになっていれば無事PWM機能が有効になっています。

# su
パスワード:
# cat /sys/kernel/debug/pinctrl/1c20800.pinctrl/pinmux-pins
:(省略)
pin 4 (PA4): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 5 (PA5): 1c21400.pwm (GPIO UNCLAIMED) function pwm0 group PA5
:(省略)
# ls /sys/class/pwm/
pwmchip0

PWMを制御するには

前回のGPIO制御同様にsysfsで制御します。基本的な制御方法はGPIOと同じです。

# ls /sys/class/pwm/pwmchip0
device  export  npwm  power  subsystem  uevent  unexport
# echo 0 > /sys/class/pwm/pwmchip0/export
# ls /sys/class/pwm/pwmchip0
device  export  npwm  power  pwm0  subsystem  uevent  unexport
# ls /sys/class/pwm/pwmchip0/pwm0/
capture  duty_cycle  enable  period  polarity  power  uevent
# cat /sys/class/pwm/pwmchip0/pwm0/enable
0

以下の項目を設定することでPWMを制御できます。

period

PWM信号の周期を設定する。nsec単位の値。

duty_cycle

PWM信号のアクティブ時間を設定する。nsec単位の値で、periodより小さい値である必要がある。

polality

PWM信号の極性を設定する。このプロパティへの書き込みは、PWMチップが変更をサポートしている場合にのみ機能する。PWMがdisableの場合にのみ変更できる。値は “normal” または “inversed” 。

enable

PWM信号の有効(1)/無効(0)を設定する。

上記項目については以下「Pulse Width Modulation (PWM) interface」の「Using PWMs with the sysfs interface」節に記載があります。

https://www.kernel.org/doc/Documentation/pwm.txt

制御内容を設計

今回の目的は「PWM制御でLEDの明るさを制御する」です。簡単にいうと「電圧のON/OFFを高速に行い、かつONとOFFの時間割合を変えることで明るさを変える」です。

調べたところ周波数は200Hz~1kHz程度が一般的な模様です。今回はせっかくのハードPWM使用なので1kHzにしてみます。

1kHzということは1/1000=0.001=1ms周期です。前述の通りperiodはnsec単位のため、1,000,000を設定します。

duty_cycleには1msの間で信号がHiになっている時間を設定します。こちらもnsec単位です。今回はduty比(ONになっている時間の割合)を10%~100%で徐々に明るくしていき100%~10%で徐々に暗くしていく、それを繰り返すこととします。

更に、10%から100%(もしくは100%から10%)に到達するまでの時間は1秒とします。2%ずつ変化させるとして、90[%]/2[%]=45、つまり1秒間に45回制御するということになるので制御間隔は1[s]/45[回]≒0.022[s]=22[ms]となります。

C言語でプログラミング

sysfsをopen/writeするという制御の大筋はGPIO編と大差ないため細かい説明は割愛します。コメントを見ながらソースを読んでみてください。

$ vi led-soft-flashing.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    int fd = -1;
    int ret;
    struct stat st = { 0 };
    unsigned long periodValue;
    unsigned long dutyValue;
    char valueStr[16];
    int strSize;
    const struct timespec tms = { 0U, 22U * 1000U * 1000U };    // 22ms

    // Unexport pwm0 if it exists.
    ret = stat("/sys/class/pwm/pwmchip0/pwm0", &st);
    if(ret == 0) {
        fd = open("/sys/class/pwm/pwmchip0/unexport",O_WRONLY);
        if(write(fd, "0", 1) != 1) {
            perror("write-1");
            exit(EXIT_FAILURE);
        }
        close(fd);
    }

    // Export pwm0.
    fd = open("/sys/class/pwm/pwmchip0/export",O_WRONLY);
    if(write(fd, "0", 1) != 1) {
        perror("write-2");
        exit(EXIT_FAILURE);
    }
    close(fd);

    // Set polarity to normal.
    fd = open("/sys/class/pwm/pwmchip0/pwm0/polarity",O_WRONLY);
    if(write(fd, "normal", 6) != 6) {
        perror("write-3");
        exit(EXIT_FAILURE);
    }
    close(fd);

    // Set period value.
    fd = open("/sys/class/pwm/pwmchip0/pwm0/period",O_RDWR);
    periodValue = 1000000;    // 1kHz
    strSize = snprintf(valueStr ,16, "%lu", periodValue);
    if(write(fd, valueStr, strSize) != strSize) {
        perror("write-4");
        exit(EXIT_FAILURE);
    }
    close(fd);

    // Set initial duty cycle value.
    fd = open("/sys/class/pwm/pwmchip0/pwm0/duty_cycle",O_RDWR);
    dutyValue = 100000;     // 10%
    strSize = snprintf(valueStr, 16, "%lu", dutyValue);
    if(write(fd, valueStr, strSize) != strSize) {
        perror("write-5");
        exit(EXIT_FAILURE);
    }
    close(fd);

    // Set pwm0 enable.
    fd = open("/sys/class/pwm/pwmchip0/pwm0/enable",O_WRONLY);
    if(write(fd, "1", 1) != 1) {
        perror("write-6");
        exit(EXIT_FAILURE);
    }
    close(fd);

    while(1) {
        // duty 10%->100%
        do {
            nanosleep(&tms, NULL);
            //
            fd = open("/sys/class/pwm/pwmchip0/pwm0/duty_cycle",O_RDWR);
            dutyValue += 20000;     // +2%
            strSize = snprintf(valueStr ,16, "%lu", dutyValue);
            if(write(fd, valueStr, strSize) != strSize) {
                perror("write-7");
                exit(EXIT_FAILURE);
            }
            close(fd);
        } while(dutyValue < 1000000);

        // duty 100%->10%
        do {
            nanosleep(&tms, NULL);
            //
            fd = open("/sys/class/pwm/pwmchip0/pwm0/duty_cycle",O_RDWR);
            dutyValue -= 20000;     // -2%
            strSize = snprintf(valueStr, 16, "%lu", dutyValue);
            if(write(fd, valueStr, strSize) != strSize) {
                perror("write-8");
                exit(EXIT_FAILURE);
            }
            close(fd);
        } while(dutyValue > 100000);
    }

    exit(EXIT_SUCCESS);
}

今回のプログラムは永久ループになっていますので停止させる際はCtrl-Cを入力してください。

LED回路の接続

前述の通りPA5ピンはUART0-RXピンと兼用です。SDカードスロットを上にしてみたときに左側の内側下から4番目のピンになります。

Lフワの様子

LED回路を接続したらプログラムを実行してみましょう。

$ gcc led-soft-flashing.c
$ sudo ./a.out

こんな感じでふわっと点滅します[1]頑張ってgif作りました

以下にオシロ画像を貼っておきます。こんな感じでdutyが変化していきます。

Hiが2V程度になっていますが、こんなもんなんでしょうかね。

今回の記事で使用した機材紹介

注釈

注釈
1 頑張ってgif作りました

コメント

Amazon プライム対象
タイトルとURLをコピーしました