こんにちは、ENGかぴです。
マイコンのソフト開発しているとPWM制御を使って電圧値を制御することがあります。PWMはPIC12F675ではタイマ機能とDOを使って生成することができます。PWMを使うと電流消費を押さえながらモーターを回転させることもできます。
PWMの考え方を応用するとブザーを鳴らすこともできます。ブザーを鳴らす方法については下記記事にまとめています。
PICマイコン(PIC12F675)を使ってブザーを鳴らす方法
PIC12F675を使ってマイコンの動きを勉強するためにPIC12F675の機能でできることについてまとめています。
PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ
PIC12F675でPWM波形を生成する方法
PWM制御はマイコンからモーターの回転数などを変更する場合に使用されることが多い技術です。マイコン単体ではモーターを回転させるほどの出力電流が出せないので、通常はトランジスタなどを使用して電流を増幅して使用します。
PWM制御について
PWMとはpulse width modulationの略でありパルス幅変調です。一定のキャリア周波数に対する波形のデューティ比(HighとLowの比率)によって電圧を調整することができます。
PWM制御によってトランジスタなどの素子がON/OFFを繰り返すことからOFF時には電流が流れないことから消費電流を抑えることもできます。
タイマー0を使用する
PWM制御は可能な限りキャリア周波数が高い(タイマーの間隔が短い)方が電圧の操作がしやすいためプリスケーラなしの場合で256usでオーバーフローするタイマーであるタイマー0を使うことでキャリア周波数1Hz以上のPWM波形を生成します。
PIC12F675はTMR0とWDTでプリスケーラを兼用となっているためWDTでプリスケーラを使用している場合はTMR0のプリスケーラとしては使用できません。
WDTはプリスケーラなしに設定しても約18msで働くため余裕があると判断してTMR0でプリスケーラを使用することを考えていきます。
PICマイコン(PIC12F675)の初期化
PICマイコンを使用するためには、マイコンをどのような機能で使用するのかを選択する初期化を行う必要があります。デューティ比が変更できるようにするためAD変換器を使用します。AD変換の使い方については下記記事を参考にしてください。
PICマイコン(PIC12F675)のAD変換とタイマーの組み合わせ
ボタンを押したときにPWM波形が出るようにするためにDIフィルタについても実装します。下記記事を参考にしてください。
PICマイコン(PIC12F675)のDIピンのフィルタの考え方
タイマー0の初期化
タイマー0の使い方について説明します。タイマー0に関係するOPTIONレジスタとINTCONレジスタ及びTMR0レジスタです。
bit2-0についてTMR0を今回は1:4で使用するために0b001をセットします。
bit3についてTMR0のプリスケーラとして使用したいので0をセットします。
bit5について内部クロックを使ってカウントしたいので0をセットします。
bit5をセットするとTMR0が動き出しますので問題になる場合は初期化部分の最終箇所でセットすると良いでしょう。
bit5についてTMR0割り込みを使用するので1をセットします。セットするとタイマー0の割り込みが動き出すので問題となる場合は、初期化の最終箇所でT0IEのビットをセットすると良いでしょう。
bit2についてTMR0割り込みが発生するとセットされます。ユーザーでクリアする必要があります。
TMR0レジスタに値を入れると入力した値からカウントが開始するため初期値を入れることで管理したいタイマーの時間が生成できます。
定義部分を実装する
タイマー0の初期値を設定することでオーバーフローまでのカウント数を設定することができます。今回はカウント200でオーバーフローさせたいので初期値の定義として#define TIME0_START 0x38(56)を定義して割り込み内部でセットしています。
初期化部分を実装する
void main(void) {
//各種初期化
ADCON0 = 0x81; //ADコンバータの電源をON。結果は右詰。
ANSEL = 0x11; //GP0をアナログ入力に使用。変換クロックは8TOSC
CMCON = 0x07; //コンパレータ使用しない
TRISIO = 0x09; //GP0,GP3入力、その他出力
GPIO = 0x00; //ポートの設定 1:High 0:Low
OPTION_REG = 0x01; //プリスケーラ4 TMR0で使用
//割り込み設定
INTCON = 0xC0; //PIE1許可
PIE1 = 0x41; //AD変換終了・TMR1割り込み許可
//TMR1起動
T1CON = BIT_CLR;
TMR1H = TIME_START_H; //タイマ初期化
TMR1L = TIME_START_L; //タイマ初期化
cnt10ms = 0;
TMR1ON = BIT_SET; //タイマ1ON
TMR1IF = BIT_CLR; //割り込みクリア
//DIフィルタ初期起動
timWait = TIME_WAIT_MAX;
while( timWait > TIME_UP){
CLRWDT();
mainTimer();
DiFilter();
}
timWait = TIME_OFF;
//TMR0を起動
TMR0 = TIME0_START;
T0IE =BIT_SET; //TMR0割り込み許可
T0IF = BIT_CLR; //割り込みクリア
//AD変換起動
GO_DONE = BIT_SET;
TMR0はOPTIONレジスタのbit5(T0CS)を内部クロックを使用する設定(0)としたとき動き出すので割り込みを使用していなくてもT0IFビットがセットされてしまいます。
割り込みを使用する場合は初期値と割り込みの許可をする際にT0IFビットのクリアをしておく必要があります。
PWMを実装する
PWM確認として2つの回路を使用します。
マイコンの出力電流でも動作可能なLEDを使ってPWMについて動作確認を行います。可変抵抗によってPWMのデューティ比が変更できるようにしてLEDの点灯が変化することが確認できる構成を考えていきます。
PWMでトランジスタTR1をON/OFFしてモーターを回す回路を考えます。モーターの電源はトランジスタのコレクタ側なのでGNDが共通であれば別の電源でも問題ありません。
TR1のベースにマイコンのPWM波形を入力しますが電流制限のため抵抗R3を入れています。この抵抗が大きすぎると増幅してもモーターが回せなくなるので調整が必要です。R4はトランジスタへの入力を安定化させることやトランジスタの漏れ電流を防止するために入れています。
PWMの回路図と出力電流に関する注意事項
SW1を押すとLED1が点灯しPWM制御を開始します。PWMはGP4から出力され可変抵抗VR1を変更すると明るさが変化します。
下記記事にシンク電流と出力電流についてまとめています。
PICマイコン(PIC12F675)のI/Oポートのシンク電流/出力電流の考え方
PWMを実装する
while(1){
CLRWDT();
mainTimer();
DiFilter();
ADFilter();
CntData = TMR0;
if( gpio3_buf){
if( CntData >= CntMax ){
GPIO4 = BIT_CLR;
}
else{
GPIO4 = BIT_SET;
}
}else{
GPIO4 = BIT_CLR;
}
}
TMR0のカウント値をメイン関数で確認しAD変換からで算出したCntMaxと比較することでPWM波形を生成しています。可変抵抗を調整しAD変換の値が大きくなるほど比較値のCntMaxが大きくなるようにすることでデューティ比が高くなります。
//--------ADフィルタ----------------------------
void ADFilter(void){
unsigned short set;
char i;
if( adfiltflg ){
adfiltflg = BIT_CLR;
di(); //割り込みを一旦禁止
AdData.buf[ AdData.wp ] = ADRESH;
AdData.buf[ AdData.wp ] = ( AdData.buf[ AdData.wp ] << 8) + ADRESL;
ei(); //割り込み禁止を解除
set = 0;
for( i = 0; i < AD_NUM; i++ ){
set += AdData.buf[ i ];
}
set = set >> 2;
if(++AdData.wp >= AD_NUM){
AdData.wp=0;
}
CntMax = (set >> 2);
//TMR0は8ビットなのでAD変換値を8ビットのデータに合わせるため2ビットシフト
if(CntMax <= TIME0_START){
CntMax = TIME0_START;
}
CntMax += HOSEI; //デューティー比の初期値を上げる補正を入れてもよい
}
}
AD変換値の移動平均をとって値を確定した後に2ビットシフトしているのはTMR0が8ビットデータであるためです。
CntMaxが初期値以下のときは初期値を採用するように条件を加えています。CntMaxの初期値を任意にすることで最低のデューティ比を決めることができます。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
// CONFIG
#pragma config FOSC = INTRCIO
#pragma config WDTE = ON
#pragma config PWRTE = OFF
#pragma config MCLRE = OFF
#pragma config BOREN = OFF
#pragma config CP = OFF
#pragma config CPD = OFF
#include <xc.h>
//--------------定数定義----------------------------
#define _XTAL_FREQ 4000000 //発振周波数 4MHz
#define TIME_START_H 0xD8 //タイマスタート位置 65536- 55536 = 10000
#define TIME_START_L 0xF0 //タイマスタート位置 65536- 55536 = 10000
#define TIME0_START 0x38 //タイマスタート位置 256 - 56 = 200
#define CNT_MAX 2
#define BIT_SET 1
#define BIT_CLR 0
#define CNT_OFF -1
#define AD_NUM 4 //移動平均フィルタのサンプリング数
#define DI_NUM 4
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define TIME_ADCNT_MAX 10 //100ms経過
#define TIME_BASE_MAX 1 //ベースタイマカウント値
#define TIME_WAIT_MAX 10
#define HOSEI 75 //PWMの初期のデューティー比をあげるための補正
typedef struct{
char wp;
unsigned short buf[ AD_NUM ];
}FILT_DATA;
//-----------------変数定義--------------------------
unsigned short CntMax; //Duty最大値一時保管用
unsigned short CntData;
FILT_DATA AdData;
FILT_DATA DiData;
char adfiltflg = BIT_CLR; //ADフィルタ起動
char difiltflg = BIT_CLR; //DIフィルタ起動
unsigned char cnt10ms;
char TimerAdStart = TIME_OFF;
char timWait = TIME_OFF;
char gpio3_buf;
//-----------------関数定義---------------------------
void ADFilter(void);
void mainTimer(void);
void DiFilter(void);
void main(void) {
//各種初期化
ADCON0 = 0x81; //ADコンバータの電源をON。結果は右詰。
ANSEL = 0x11; //GP0をアナログ入力に使用。変換クロックは8TOSC
CMCON = 0x07; //コンパレータ使用しない
TRISIO = 0x09; //GP0,GP3入力、その他出力
GPIO = 0x00; //ポートの設定 1:High 0:Low
OPTION_REG = 0x01; //プリスケーラ4 TMR0で使用
//割り込み設定
INTCON = 0xC0; //PIE1許可
PIE1 = 0x41; //AD変換終了・TMR1割り込み許可
//TMR1起動
T1CON = BIT_CLR;
TMR1H = TIME_START_H; //タイマ初期化
TMR1L = TIME_START_L; //タイマ初期化
cnt10ms = 0;
TMR1ON = BIT_SET; //タイマ1ON
TMR1IF = BIT_CLR; //割り込みクリア
//DIフィルタ初期起動
timWait = TIME_WAIT_MAX;
while( timWait > TIME_UP){
CLRWDT();
mainTimer();
DiFilter();
}
timWait = TIME_OFF;
//TMR0を起動
TMR0 = TIME0_START;
T0IE =BIT_SET; //TMR0割り込み許可
T0IF = BIT_CLR; //割り込みクリア
//AD変換起動
GO_DONE = BIT_SET;
while(1){
CLRWDT();
mainTimer();
DiFilter();
ADFilter();
CntData = TMR0;
if( gpio3_buf){
if( CntData >= CntMax ){
GPIO4 = BIT_CLR;
}
else{
GPIO4 = BIT_SET;
}
}else{
GPIO4 = BIT_CLR;
}
}
}
//-----------------割り込み関数-------------------------
void __interrupt() intr( void){
if( TMR1IE == 1 && TMR1IF == 1){ //タイマ1の割り込みであるか
TMR1IF = 0;//割り込みフラグをクリア
++cnt10ms;
difiltflg = BIT_SET;
TMR1ON = 0;
TMR1H = TIME_START_H;//タイマ初期化
TMR1L = TIME_START_L;//タイマ初期化
TMR1ON = 1;//タイマ1ON
}
if( T0IE == 1 && T0IF == 1){
T0IF = 0;
TMR0 = TIME0_START;
}
if( ADIE == 1 && ADIF == 1){
ADIF = 0;
adfiltflg = BIT_SET;
}
}
//----------タイマー管理----------------------------------
void mainTimer(void){
if( cnt10ms >= TIME_BASE_MAX){
cnt10ms -=TIME_BASE_MAX;
if( TimerAdStart > TIME_UP ){
--TimerAdStart;
}
if( timWait > TIME_UP ){
--timWait;
}
}
if( TimerAdStart == TIME_UP){
if( GO_DONE == BIT_CLR){
GO_DONE = 1;
TimerAdStart = TIME_ADCNT_MAX;
}
}
}
//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){
if( difiltflg ){
difiltflg = BIT_CLR;
DiData.buf[ DiData.wp ] = GPIO3;
if( DiData.buf[ 0 ] == DiData.buf[ 1 ] &&
DiData.buf[ 1 ] == DiData.buf[ 2 ] &&
DiData.buf[ 2 ] == DiData.buf[ 3 ] ){
//比較して一致したら値を採用
gpio3_buf = DiData.buf[ 0 ];
}
if( ++DiData.wp >= DI_NUM) DiData.wp = 0;
}
}
//--------ADフィルタ----------------------------
void ADFilter(void){
unsigned short set;
char i;
if( adfiltflg ){
adfiltflg = BIT_CLR;
di(); //割り込みを一旦禁止
AdData.buf[ AdData.wp ] = ADRESH;
AdData.buf[ AdData.wp ] = ( AdData.buf[ AdData.wp ] << 8) + ADRESL;
ei(); //割り込み禁止を解除
set = 0;
for( i = 0; i < AD_NUM; i++ ){
set += AdData.buf[ i ];
}
set = set >> 2;
if(++AdData.wp >= AD_NUM){
AdData.wp=0;
}
CntMax = (set >> 2); //TMR0は8ビットなのでAD変換値を8ビットのデータに合わせるため2ビットシフト
if(CntMax <= TIME0_START){
CntMax = TIME0_START;
}
CntMax += HOSEI; //デューティー比の初期値を上げる補正を入れてもよい
}
}
//---------------------end file----------------------------
関連リンク
PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。
PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
最後まで、読んでいただきありがとうございました。
TMR0のプリスケーラを使用しないことや1:2にして間隔を細かくしてもよいのですが、割り込みが増えてしまうことや精度が取れなくなるためPIC12F675において内部クロックを使用する場合は誤差に注意が必要です。