C言語

RaspberryPiでPWMを使いこなす①(応用編)

スポンサーリンク

こんにちは!駆け出しエンジニアのまっきーです。

今回はハードウェア方式、ソフトウェア方式のPWMを制御していきたいと思います。

まだPWMを使いこなせていないなという人は、ぜひ見てください!

以下の課題に取り組んでいきます!課題はこちらのサイトでダウンロードできます。

課題一覧

  1. ソフトウェア方式PWMでデューティ比をかえる
  2. ソフトウェア方式PWMで約440kHzの矩形波を発生させる
  3. ハードウェア方式のPWMで約440kHzの矩形波を発生させる

PWM制御については今までも何度か行ってきましたが、復習もかねて、必要な関数などを復習しましょう!

PWMって何?という方は過去の記事をご覧ください。

復習

PWM使用時に必要な関数

ソフトウェア方式

softPwm.hソフトpwmを使用する際に必要なヘッダーファイル
softPwmCreate(int pin,int initialvalue,int pwmRange)initialvalueにはpwm信号のHIGHの長さを設定。pwmRangeはデューティ比の階調を設定
softPwmWrite(int pin,int value)GPIOを指定し、デューティ比(パルスONの長さ)を設定

ハードウェア方式

pinMode(int pin,int mode)GPIOを指定し、PWM_OUTPUTモードに設定
pwmSetClock(int divisor)内部クロックを分周する値を設定
pwmSetRange(unsigned int range)デューティ比の階調を設定
pwmSetMode(int mode)PWM_MODE_BAL or PWM_MODE_MSを設定
pwmWrite(int pin, int value)GPIOを指定し、デューティ比(パルスONの長さ)を設定

ピン配置図

画像に alt 属性が指定されていません。ファイル名: image-21.png

電子回路

回路図は、前回の記事に引き続き、付け加える形で作成しています。

ラズパイ4を使っている方は、スイッチに3kΩ程の抵抗をつけてください。

ラズパイ3,3B+を使用している方は内部抵抗が使えるので下の図の通りで大丈夫です。

圧電ブザーはGPIO18とつないでいます。今回使用した圧電ブザーは、下記のセットに付属していたものです。

https://amzn.to/2R8guHm

以下、GPIOの対応表です

スイッチ,LED番号GPIO
LED0GPIO23
LED1GPIO24
LED2GPIO20
LED3GPIO21
SW0GPIO25
SW1GPIO17
SW2GPIO27
SW3GPIO5
SW4GPIO6
SW5GPIO13
SW6GPIO19
SW7GPIO26

ソフトウェア方式PWMでデューティ比をかえる

タクタルスイッチに対応したデューティー比でLEDを点灯させるプログラムです。スイッチが押されていないときのデューティー比は0です。

実際に私が作成したソースコードは以下の通りです。

ソースコード

softPwmCreate()関数を、LED、スイッチ初期化の前に置いていたのですが、うまく動作しませんでした。

しかし、下のコードの位置に置き換えたところ、普通に動作しました。原因としては、LEDの出力設定が終わっていないのに、PWMを作成したためだと思います。初歩的なミスです…

//ソフトPWMの設定 softPwmCreate(GPIO,パルスON初期値,range)
//softPwmWrite(GPIO,パルスONの長さ)

//PWM周波数は、100Hz
//f = 1 / T よりT = 0.01s = 10ms
//T = 100us * range(100)  100us*100=10ms

#include <stdio.h>
#include <wiringPi.h>
#include <softPwm.h>//softpwmの設定を忘れない


int led[4] = {23,24,20,21};//LED0,LED1,LED2,LED3
int sw[8] = {25,17,27,5,6,13,19,26};//SW0~SW7


int main(void){
	//wiringpi初期化
	if(wiringPiSetupGpio() == -1) return 1;

	int i;
	double duty[] = {12.5,25.0,37.5,50,62.5,75,87.5,100};//デューティー比の値を配列に格納
	int ton = 0;
	int range = 100;
	
	//LEDを出力に設定
	for(i=0;i<4;i++){
		pinMode(led[i],OUTPUT);	}
	//SWを入力、プルアップに設定
	for(i=0;i<8;i++){
		pinMode(sw[i],INPUT);
		pullUpDnControl(sw[i],PUD_UP);
	}
	//PWM初期設定
	softPwmCreate(led[0],ton,range);
	while(1){
		for(i=0;i<8;i++){
			if(digitalRead(sw[i]) == 0){
				softPwmWrite(led[0],duty[i]);
			}else{
				softPwmWrite(led[0],0);
			}
		}
	}
	return 0;
}

著者のソースコードと見比べてみます。(引用元

#include <stdio.h>              //入出力
#include <stdlib.h>             //一般ユーティリティ
#include <wiringPi.h>           //wiringPi
#include <softPwm.h>            //ソフトウェア方式PWM(wiringPi)

#define LED0    23                            //GPIO23をLED0と定義
const int swGpio[8] = {4,5,6,26,17,27,20,21}; //SW GPIOを配列で定義
/* プロトタイプの定義 */
int SwChk(void);

int main (void){
    int i,swData;
    int pwmRange = 100;        //100Hzの周期は10ms、pwmRange*100us=10ms
    int initialValue = 0;      //softPwmCreate関数実行時には出力信号0V
    int value;                 //softPwmWrite関数の引数
    wiringPiSetupGpio();       //BCMのGPIO番号を使用
    pinMode(LED0, OUTPUT);     //LED0を出力に設定
    for(i=0;i<8;i++){          //SW0-SW7を入力に設定
        pinMode(swGpio[i], INPUT);}
    for(i=0;i<8;i++){          //SW0-SW7をプルダウン抵抗をつける
        pullUpDnControl(swGpio[i],PUD_DOWN);}
    softPwmCreate(LED0,initialValue,pwmRange); //ソフトPWMの設定

    while(1){
        swData = SwChk();
        value = (double)swData * 12.5 + 0.5;  //四捨五入
//      printf("%d \n",value);
        softPwmWrite(LED0,value);   //デューティ比の設定と出力
    }
    return EXIT_SUCCESS;
}

/***************************
 関数名  int SwChk(void)
 引数    なし
 戻り値
 0 何も押されていない
 1 SW0が押された
 2 SW1が押された
 3 SW2が押された
 4 SW3が押された
 5 SW4が押された
 6 SW5が押された
 7 SW6が押された
 8 SW7が押された
 SW0からSW7の状態を取得する関数。
 同時に2つ以上のスイッチが押された場合、
 LSB側のSWを優先する。
 ***************************/
int SwChk(void){
    int i,swData=0;
    for(i=0;i<8;i++){    //SW0からSW7まで検査
        if (digitalRead(swGpio[i]) == HIGH){
            swData=i+1;
            break;
        }else{
            swData =0;
        }
    }
    return swData;
}
*/

SwChk()という、スイッチの入力状態を取得する関数を作成しています。リターンで入力状態を返すのですが、for文で0から順番にチェックしているため、最下位bitが優先されるということなんですね。

この発想はなかったです。そして、汎用性が高そうです。

次の問題に行きましょう!

ソフトウェア方式PWMで約440kHzの矩形波を発生させる

デューティー比の指定のみです。計算方法はソースコードの文頭に記載しておきます。

ソースコード

/*******公式一覧******************************************************
T:周期 f:周期 range:duty比の階調
T = 最小変化幅(分解能) * range 
注 ソフトウェア方式の最小変化幅は100μs,ハードウェア方式は自由に決定
f = 1 / T = 1 / (最小変化幅 * range)
********************************************************************/

//上記の公式に当てはめて、
//T = 100us * rangeより
//f = 1 / (100us * range)
//400 = 1 / (100us * range)
//range = 22.7272  100us=0.0001s

//ここからプログラムスタート
#include <stdio.h>
#include <wiringPi.h>
#include <softPwm.h>//softpwmの設定を忘れない

#define PWM 18

int led[4] = {23,24,20,21};//LED0,LED1,LED2,LED3
int sw[8] = {25,17,27,5,6,13,19,26};//SW0~SW7


int main(void){
	//wiringpi初期化
	if(wiringPiSetupGpio() == -1) return 1;

	int i;
	int ton = 0;
	double range = 22.7;
	

	//LEDを出力に設定
	for(i=0;i<4;i++){
		pinMode(led[i],OUTPUT);	}
	//SWを入力、プルアップに設定
	for(i=0;i<8;i++){
		pinMode(sw[i],INPUT);
		pullUpDnControl(sw[i],PUD_UP);
	}
	softPwmCreate(PWM,ton,range);

	while(1){
			if(digitalRead(sw[0]) == 0){
				softPwmWrite(PWM,range/2);
			}else{
				softPwmWrite(PWM,0);
			}
	}
	return 0;
}

このような音を鳴らすことができました!

少し音に歪み?のようなものがありますね。この理由はもう少し先でわかります。

ハードウェア方式PWMで約440kHzの矩形波を発生させる

先ほどの問題をハードウェア方式で行えということですね。

ハードウェア方式実装の手順を再確認して、臨みます。

ソースコード

//6-3 ハードウェア方式で約440Hzの矩形波を発生させる
//条件として、デューティー比50%、range=100
//T = 100us * range
//f = 1 / (最小変化幅 * 100)
//440 = 1 / (最小変化幅 * 100)
//最小変化幅=0.00002273=22.7㎲となる

#include <stdio.h>
#include <wiringPi.h>

#define PWM 18
#define CLOCK  19200000.0

int led[4] = {23,24,20,21};//LED0,LED1,LED2,LED3
int sw[8] = {25,17,27,5,6,13,19,26};//SW0~SW7


int main(void){
	//wiringpi初期化
	if(wiringPiSetupGpio() == -1) return 1;

	int i;
	double minwidth = 22.7;//最小変化幅
	double divisor = minwidth * CLOCK;//divisor = 19.2MHz * 最小変化幅
	int range = 100;//階調
	pinMode(PWM,PWM_OUTPUT);//ピンのモードをPWMに
	pwmSetClock(divisor);
	pwmSetRange(range);
	pwmSetMode(PWM_MODE_MS);//マークスペースモードに設定


	//LEDを出力に設定
	for(i=0;i<4;i++){
		pinMode(led[i],OUTPUT);	}
	//SWを入力、プルアップに設定
	for(i=0;i<8;i++){
		pinMode(sw[i],INPUT);
		pullUpDnControl(sw[i],PUD_UP);
	}

	while(1){
			if(digitalRead(sw[0]) == 0){
				pwmWrite(PWM,range/2);
			}else{
				pwmWrite(PWM,0);
			}
	}
	return 0;
}

このような音が鳴りました。同じ周波数で設定したのですが少しだけ音色が違います。

この違いは、最小変化幅(分解能)によるものだそうです。
●ソフトウェア方式の最小変化幅は 100us のため、周期 T は 2.3ms または 2.2ms です。
2.3ms = 434.8 Hz, 2.2ms = 454.5 Hz となり、音が低くなったり、高くなったりします。
●ハードウェア方式の最小変化幅が 10us の場合、周期 T は 2.27ms です。
2.27ms = 440.5 Hz になります。

先ほどの歪みは周波数が安定していないということなんですね!ハードウェア方式の方が精度が高いということも改めて確認できました!

まとめ・反省

・各処理ごとで適切に関数分けをすることにより、コードがシンプルになる

・ハードウェア方式の方がPWM信号の精度が高い

・小数がでてきたため、普通にdouble型を使用したが、関数内ではint型に変形されているものと思われる

→精度が変わってしまうため、注意が必要。

いかがでしたでしょうか。PWM信号作成のための計算はもう大丈夫ですか?

次は今回できなかった電子ピアノやオルゴールに挑戦できたらと思います。

ありがとうございました。

スポンサーリンク

-C言語