モータの鳴らし方byHanDen

電子工作の初心者がモータを演奏したりVVVF音を再現したりする方法を紹介するブログ ホビー向けの電子工作を基礎から書いていきます 記事のミス等のお問い合わせはTwitterにてお願いします。 当ブログを参考に製作をする際は必ず自己責任にて行ってください 当ブログを参考にしたことによる損害等の責任は一切負いません ドメイン取得につきURLを http://vvvf.blog.jp から http://blog.henden.net に変更しました

工作の基礎

準応用編 第1回 マイコン内蔵LED “WS2812B” の制御方法

今回はマイコンを使ってマイコン内蔵のLEDであるWS2812Bを制御します。今回紹介するLED WS2812Bの特徴はマイコンなどから信号を与えることで各LEDを個別で制御できかつ安価である(AliExpress1つ当たり10円を切る価格)で売られているという所です。数珠接続で個別制御できるため安価なLEDテープを大進化させたLEDともいえると思います。

 一見最強そうに見えるこのLED実はマイコンから制御するのが一筋縄にはできないという問題があります。(本来は専用のコントローラを使います)今回はこのWS2812B搭載のLEDテープをAVRマイコンとSTM32マイコンで制御する方法を考えていきます。

 

 

制御信号の形

 まずはWS2812Bのデータシートを見てみます。

http://www.world-semi.com/DownLoadFile/108

こちらが製造元の新しいバージョンとみられるデータシート

https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf

そしてこちらがadafruitとか秋月に掲載されているデータシートです。

ややこしいことに、信号生成に関わる箇所のパラメータがこの2者で違うことを覚えておいてください。
 データシートを見ていくとUARTでもSPIでもI2Cでも無い、専用のデータ形式であることがわかります。具体的な信号の形(1bit)は下図のような感じです。

 1

そしてこのbitが以下のように連続して送られることで1つのLEDの制御信号になります。
2 

bitを緑、赤、青の順に連続的に並べたのみでスタートビットやストップビットといったものは無いのが特徴と言えます。

 そして複数のLEDの制御はこの24bitのデータを連続的に並べることで行います。LEDの接続と信号の順番を図で表すと以下のようになります。
3

 Amazonとかの販売ページにはアドレス指定可能とか書かれていますが、実際にはアドレスを指定して制御するわけではなく、手前のLEDから順番にデータを送る形式となっています。

 このLEDの制御における注意点としてはLEDの点灯状態を変える際にはLOWが一定時間以上(50us ot 280us)続くリセット信号を送らないといけないことです。逆に言い換えるとリセットされない時間であればLOWが続いても大丈夫ということです。実はこの仕様のおかげでAVRマイコンでも制御することができます。

 

制御信号の作り方

 続いてどうやってこの信号を作り出すかを考えていきます。制御信号の形上UARTとかI2CとかSPIをそのまま使うことはもちろんできません。また、信号速度的にArduinodigitalWriteとかは使えないのはもちろん、レジスタを叩いてもdelayとか割り込みでの処理も困難です。アセンブラを使えばクロック単位で処理できるので信号生成できますが、難しいし処理も重い、でもクロック単位レベルの高速な信号生成が必要。調べてみるとSPIの複数bitを使って信号生成してる例があったので、今回はSPIを使ってAVRSTM32で信号生成をしてみることにします。

 

 まずはSPI通信の仕様を軽く説明します。

SPI通信はUARTと違って4つのピンを使って制御します。そのピンの内訳はクロック(SCK) マスターアウトプットスレーブインプット(MOSI) マスターインプットスレーブアウトプット(MISO) スレーブセレクト(SS(ーー))です。通信を行うときはSSーーピンをLOWにして、SCKピンにクロックを掛けてクロックと同期した信号を送信します。タイミングチャートを下に示してみます。

4

 ここで、ややこしいことが起きてしまいます。それはMOSI(マスター送信ピン)がアイドル時に状態が不定になるということです。その他SCKピンのアイドル時のLOW,HIGHの差や先行端、後行端を採るかの差はマイコン間などでSPI通信を行うときは合わさないといけませんが、今回のLED制御には本質的には関わりません。 

 今回WS2812Bを制御するにあたって使うピンはMOSIピンのみです。他のピンは使いません。送信するデータの工夫でWS2812Bの制御信号を作り出します。

 

今回はSPIのデータの4bitWS2812B1bitとして使います。送り出すパケット形状より下図のようにHIGHになるbitの数を調整することで0のパケットと1のパケットを作り出します。

5

この形状の出力を作り出すデータは0パケットの場合0b1000(0x80) 1パケットの場合0b1110(0xE)になります。ですが、そう単純ではありません。SPIのアイドル時の状態が不定であるためです。ここで、今回使用するマイコンのデータシートを見てみます。

 

AVRマイコン(ATmega328P) https://avr.jp/user/DS/PDF/mega328P.pdf

STM32マイコン(STM32F446)https://www.st.com/content/ccc/resource/technical/document/reference_manual/4d/ed/bc/89/b5/70/40/dc/DM00135183.pdf/files/DM00135183.pdf/jcr:content/translations/en.DM00135183.pdf

 

 まずはAVRマイコンで制御信号を作り出す方法を考えるため、データシートのSPIのタイミングチャートが書かれている121ページを見てみます。一応こちらにも引用しておきます。

AVRデータシート

 このデータシートを見るとAVRマイコンではMOSIのアイドル時の状態はHIGHになってるように見えます。確認のために実際にオシロスコープで出力波形を確認してみたいと思います。(ロジックアナライザー持ってないのでオシロを使います。)
AVR波形

 送ってるデータは0xEE(0x11101110)です。そして、信号が始まる前と後はHIGHになってることがわかります。これより、データシート通りアイドル時がHIGHになっていることがわかりました。

しかし、WS2812Bはアイドル時がLOWです。そのため、そのままではWS2812Bは動かせません。そこで、AVRマイコンでWS2812Bを制御する場合信号を反転(つまりNOTをかける)してアイドル時がLOWの信号を作りだします。

また、信号を反転させるので送るbitも反転します。つまり最終的に送る形が0b1000(WS2812B0)の場合0b0111 0b1110(WS2812B1)の場合0b0001となり16進数で表すとそれぞれ「0パケットは0b0111 0x7」「1パケットは 0x0001 0x1となります。最終的に出力した波形はこんな感じです。

AVR波形2

これによりAVRマイコンでWS2812Bを制御する信号が生成できました。また、これら図では少々わかりにくいですが、AVRの性能上8bit単位で信号に間隔が空いてしまい(LOWが長く続いてる)ます。ただし、WS2812Bの仕様上50usまでは許容されているので問題ありません。

 

 続いてSTM32マイコン(STM32F446)で制御信号を作り出すために、データシートのSPIのタイミングチャートが書かれている856ページを見てみます。こちらにも引用します。

STMデータシート

 このタイミングチャートを見るとMOSIのアイドル時の状態がHIGHLOWも両方書かれていてどちらかがわかりません。つまり、データシート上は不定というわけです。ですが、タイミングチャートのMISO(スレーブ側は出力になる)を見ると後行端採取(CPHA=1)の時はLSBが少し伸びるとみられる表記が見られるので、この条件で実験してみました。結果は下図のようにLSBの状態が次のデータの先頭まで続いていました。(他の条件での実験結果は別記事で紹介します)
STM32後端LSB0-1
STM32後端LSB0
STM32後端LSB1-1
STM32後端LSB1

WS2812Bの場合は必要な信号が「0パケットで0b1000 」「1パケットで0b1110」となっておりLSBが必ず0となります。そしてLSBのステートがアイドル時のステートとなるためアイドル時も必ず0となります。そのため、STM32マイコンで制御する場合はAVRと異なり信号の反転は不要です。また反転していないので、送り出すデータも出力波形と同様に「0パケットで0b1000 0x8」「1パケットで0x1110 0xEとなります。また、STM32マイコンではDMAが使えることもあり、8bitの間での信号の伸びが無く連続した信号が生成できています。(DMAを使わない場合でも伸びはほぼ見られませんでした)

 

最後にSPIの通信速度の設定です。

 AVRマイコンにおいてもSTM32マイコンにおいてもSPIの 通信速度(クロック)はマイコンのクロックに依存します。SPIのクロックはマイコンのクロックの1/2n(2 <= n <= 7(AVR),8(STM32)) となります。WS2812Bの場合1パケット(4bit)の長さが大体900nsから1400ns程度となっており、これから逆算するとSPIの通信速度は4.44Mbpsから2.86Mbps程度となります。筆者の環境では16MHzで駆動しているATmega328Pの場合SPIのクロックはマイコンのクロックの1/4(分周1/4)4MHz(4Mbps) で正常に作動しています。また、ペリフェラルクロック42MHzSTM32マイコンの場合はペリフェラルクロックの1/16(分周1/16)2.625MHz(2.625bps)と新版のデータシート範囲から少々ずれがありますが正常に動作しています。(ただし、個体により動かない可能性はあるので注意必要だと思います)

 

最後にWS2812BAVRマイコンとSTM32マイコンで使う場合のまとめです。

 

AVR(ATmega328P)

 アイドル時がHIGHのため信号にロジックICなどでNOTを掛ける必要がある。

 送信データ

0パケットで0x7 0b0111

1パケットで0x1 0b0001

 パケット間(SPI8bit8bitの間)に間隔が空く(LOWが長く続く) 

 

STM32(F446)

 後行端採取CPHA=1(CubeMXClockPhase : 2Edge)に設定

 アイドル時はLSBstate維持のため反転不要

 送信データ

0パケットで0x8 0b1000

1パケットで0xE 0b1110

 パケット間に間隔が空かずきれいな波形が出力される

 

共通

 SPIクロック(通信速度) 2.86Mbps~4.44Mbps程度

  多少の誤差は許容されるが、分周で合わせない場合マイコンのクロック調整も要検討

 SPIMSB First  SPIMaterモード

 SCKのアイドル時の状態はどちらでもOK

MOSIWS2812BDIN(通信ピン)に接続

データ順は緑8bit 8bit8bitの順の24bit 

複数LEDの場合は手前側のLEDのデータ(24bit)から送る

50us~280us以上のLOWが連続するとデータ転送終了

 

例:STM32マイコンで6個のLEDを赤緑青 赤緑青の順にフル点灯させる場合以下のような配列のデータをDMAで流します。

uint8_t data[72] = {0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE,

                    0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,

                    0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88,

                    0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE,

                    0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,

                    0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88, 

};

 

 2つのマイコンで比較すると多くのLEDを正確に制御する場合はDMAが使えるSTM32マイコンを使う方が良く、少ないLEDを簡単に制御する程度であればAVRマイコンでもできるといったところだと思います。必要な用途に応じて使い分けると良いでしょう。

当ブログのword版データ

本ブログの記事のWORD版のデータです。こちらの方が見やすい記事も結構あると思います。

回路編第110

https://www.dropbox.com/s/cj1y904u3lue8uh/%E5%9B%9E%E8%B7%AF1.docx?dl=0

 

回路編第11~13

https://www.dropbox.com/s/m9rkc8nasnip4j3/%E5%9B%9E%E8%B7%AF2.docx?dl=0

 

KiCad

https://www.dropbox.com/s/46siyvylqkjqvsd/kicad%E3%83%9E%E3%83%8B%E3%83%A5%E3%82%A2%E3%83%AB.docx?dl=0

 

マイコン編

https://www.dropbox.com/s/nvd7f7o4a08r5rr/%E3%83%9E%E3%82%A4%E3%82%B3%E3%83%B3.docx?dl=0

 

本データは趣味で電子工作を行う人向けの記事です。そのため、業務や研究、競技会などでの使用はご遠慮ください。また、記事の内容は自己責任の下で使用をお願いいたします。本記事を参考にして損害が発生した場合でも管理人は責任を負いません。

電子工作のキソ 目次

いままでに書いた、モータ演奏を行うための基礎事項の記事の目次です。

 

1.回路編

1回 電子部品の概要

http://vvvf.blog.jp/archives/2366464.html

 

2回 抵抗器

http://vvvf.blog.jp/archives/2369140.html

 

3回 コンデンサ・コイル

http://vvvf.blog.jp/archives/2385100.html

 

4回 ダイオード

http://vvvf.blog.jp/archives/2498755.html

 

第5回 半導体スイッチ

http://vvvf.blog.jp/archives/2571354.html

 

第6回 半導体スイッチの回路

http://vvvf.blog.jp/archives/2677434.html

 

7回 MOSFETIGBTの駆動回路

http://vvvf.blog.jp/archives/2677434.html

 

8回 ゲートドライバ

http://vvvf.blog.jp/archives/3313214.html

 

9回 トランジスタ

http://vvvf.blog.jp/archives/3466503.html

 

10回 整流回路

http://vvvf.blog.jp/archives/3881339.html

 

11回 ゲートドライバIC(L6384E)の使い方ver2 その1

http://vvvf.blog.jp/archives/6572197.html

 

12回 ゲートドライバICの使い方ver2 その2

http://vvvf.blog.jp/archives/6620551.html

 

13回 ゲート抵抗の選び方

http://vvvf.blog.jp/archives/6770493.html

 

 

2.KiCadの使い方

1回 KiCadの初期設定

http://vvvf.blog.jp/archives/4052091.html

 

2回 回路図エディタEeschemaの使い方 その1 回路図を描くチュートリアル

http://vvvf.blog.jp/archives/4283557.html

 

3回 回路図エディタEeschemaの使い方 その2 PCB Parts Library の使い方

http://vvvf.blog.jp/archives/4305504.html

 

4回 回路図エディタEeschemaの使い方 その3 ピン設定の変更

http://vvvf.blog.jp/archives/4380666.html

 

5回 プリント基板エディタPcbnewの使い方 その1 

http://vvvf.blog.jp/archives/4644669.html

 

6回 プリント基板エディタPcbnewの使い方 その2

http://vvvf.blog.jp/archives/4724854.html

 

7回 プリント基板エディタPcbnewの使い方 その3

http://vvvf.blog.jp/archives/4803088.html

 

8回 プリント基板エディタPcbnewの使い方 その4

http://vvvf.blog.jp/archives/5087007.html

 

9回 フットプリントエディタの使い方

http://vvvf.blog.jp/archives/5188288.html

 

10回 ユニバーサル基板や切削基板でのKiCadの設定

http://vvvf.blog.jp/archives/5190101.html

 

 

3.制御編

第1回  マイコンのハードウェア

http://vvvf.blog.jp/archives/6080429.html

 

2回 マイコンで使うビット演算

http://vvvf.blog.jp/archives/6216594.html

 

3回 ビットシフト

http://vvvf.blog.jp/archives/6240219.html

 

4回 AVRマイコンの環境づくり1 ライターのドライバとAtmel Studioのインストール

http://vvvf.blog.jp/archives/6261293.html

 

5回 AVRマイコンの環境づくり2 AtmelStudioの日本語化とAtmel Studioの初期設定

http://vvvf.blog.jp/archives/6262931.html

 

6回 Atmel Studioでのファイル作成とFuse設定

http://vvvf.blog.jp/archives/6402405.html

 

7回 AVRマイコンのデジタル入出力

http://vvvf.blog.jp/archives/7126511.html

 

8回 マイコンのPWM出力 その1 可変周波数PWMの理論とパラメータ決定

http://vvvf.blog.jp/archives/7483091.html

 

9回 マイコンのPWM出力 その2

http://vvvf.blog.jp/archives/7662206.html

 

10回 マイコンのタイマー割り込み その1

http://vvvf.blog.jp/archives/7860243.html

 

11回 マイコンのタイマー割り込み その2

http://vvvf.blog.jp/archives/7860414.html

 

12回 AVRマイコンの割り込みのまとめ

http://vvvf.blog.jp/archives/8031672.html

 

13回 AVRマイコンのシリアル通信

http://vvvf.blog.jp/archives/8456549.html


制御編 第13回 AVRマイコンのシリアル通信

 

 前回までは何回かに分けて割り込み処理のお話をしてきましたが、今回は内容がガラッと変わってシリアル通信のお話を行います。ちなみに、Atmega328Pには一般的にシリアル通信と言われる「USART」のほかにも、「I2C」や「SPI」もありますが、今回紹介するには「USART」です。初めに、この3つの通信方式を筆者の偏見で軽くまとめてみます。

項目

USART

I2C

SPI

使用箇所

主に機器,基板間

主に基板内

主に基板内

伝送可能距離

長い

短い

短い

接続数

11が基本

1対複数が可能

1対複数が可能

通信線数

2(TX,RX)

2(SDA,SCL)

3(SCK,MISO ,MOSI)

+接続数(SS)

安定性

高い

UARTより低い

UARTより低い

通信速度

設定次第

設定次第

設定次第

 筆者の感覚的にまとめるとざっとこんな感じです。USART」と「I2C」「SPI」通信の最大の差は、1対複数のバス通信が規格としてサポートされているかいないかと言えるでしょう。しかし、引き換えに「I2C」と「SPI」は通信可能な距離が短く、距離が延びると安定性が非常に低くなります。筆者の経験上、I2Cは数十センチの距離でもモータなどのノイズでエラーが発生し正しく通信できません。(SPIArduinoのシールド程度の距離でしか使用していないので不明です)Arduino系の分野ではI2C」はセンサなどとの通信、「SPI」はシールドとの通信に使われることが多いです。また、SPI通信はAVRマイコンのシリアル書き込みにも使われていますね。

 そして、今回紹介する「USART」は基本的には11の通信ですが、工夫をすることにより、1対複数の通信も実装することは可能です。これにより比較的長い距離でも高い安定性で複数の子に対して通信を行うことも可能というわけです。なので、筆者はこの方法でモータ演奏基板の通信を行っています。(I2CSPIは好みの問題で自分が実装する箇所には使っていません)

 前置きはこの程度にしておいて、実際にAVRマイコンでのシリアル通信の使い方の紹介をしていきたいと思います。とはいえ、基本的にシリアル通信は理論的な個所はほぼなく、条件次第で通信速度設定時に計算を行う程度となっているので、今回はレジスタの設定をメインに紹介を行いたいと思います。

 シリアル通信のレジスタの設定はatmega328Pのデータシート(https://avr.jp/user/DS/PDF/mega328P.pdf )P138~P141に書かれています。このうちシリアル通信の動作の設定を行うレジスタは「UCSR0A」「UCSR0B」「UCSR0C」の3つ、通信速度の設定を行うレジスタは「UBRR0(12bit必要なのでデータシートではUBRR0LUBRR0Hと分かれているが、プログラム上は一括で書ける)となっており、通信データが入るレジスタが「UDR0」となっています。

 初めに設定のレジスタを見ていきましょう。

UCSR0A

ビット

項目名

設定内容

設定値(書き込み)

7

RXC

USART受信完了フラグ:USARTのデータの受信が完了すると1が設定されます。受信完了割り込みを使わない場合は、このビットを読み込むことで、データが届いているかの判定を行います。受信完了割り込みを使う場合はこのビットは1にする必要があります。

受信完了割り込みを使う場合:1

使わない場合:0

6

TXC

USART送信完了フラグ:USARTのデータの送信が完了すると1が設定されます。送信完了割り込みを使う場合はこのビットは1にする必要があります。

送信完了割り込みを使う場合:1

使わない場合:0

5

UDRE

USART送信データレジスタ空きフラグ:送信のデータレジスタ(UDR0)データを受け取り可能な時に1が設定されます。データを送信するときはこのビットを読み込み、1であることを確認してから、データをUDR0に送りますデータレジスタ空割り込みを使う場合はこのビットは1にする必要があります。

データレジスタ空割り込みを使う場合:1

使わない場合:0

4

FE

フレーミング異常フラグ:フレーミング異常が発生した時に1が設定されますが、特に使いません

0

3

DOR

データオーバーラン発生フラグ:オーバーラン状態が発生した時に1が設定されますが特に使いません

0

2

UPE

バリティー誤りフラグ:受信したデータがバリティーチェックを通らかった(データが破損している)場合に1が設定されます。Arduinoとの通信(関数を使う場合)では使えません。

0

1

U2X

倍速許可:非同期動作でシリアル通信の速度を倍にする設定ですが、特に必要性がないので使いません

倍速無効:0

倍速許可:1

0

MPCM

複数プロセッサ通信動作:複数のマイコンを接続し到着データの選別を行う機能です。9bit通信が必須となり、Arduinoなどへの互換性確保が難しいので、今回は使いません

無効:0

無効:1

 

UCSR0B

ビット

項目名

設定内容

設定値(書き込み)

7

RXCIE

受信完了割り込み許可:受信完了の割り込みを許可するレジスタです。受信完了割り込みを使うときは、受信完了フラグRXC1を設定する必要があります。

受信完了割り込みを使う場合:1

使わない場合:0

6

TXCIE

送信完了割り込み許可:送信完了の割り込みを許可するレジスタです。送信完了割り込みを使うときは、送信完了フラグTXC1を設定する必要があります。

送信完了割り込みを使う場合:1

使わない場合:0

5

UDRIE

送信データレジスタ空き割り込み許可:データレジスタ空割り込みを許可するレジスタです。データレジスタ空割り込みを使うときは、送信データレジスタ空きフラグUDRE1を設定する必要があります。

データレジスタ空割り込みを使う場合:1

使わない場合:0

4

RXEN

受信許可:シリアル通信の受信部の動作を許可します。シリアルでデータを受信する場合は1を設定しなければなりません。

データを受信する場合:1

受信を行わない場合:0

3

TXEN

送信許可:シリアル通信の送信部の動作を許可します。シリアルでデータを送信する場合は1を設定しなければなりません。1対複数のシリアル通信を行う場合は、通常時は無効(0)にしないとIOが短絡するので注意が必要です。

データを送信する場合:1

送信を行わない場合:0

2

UCSZ2

データビット長選択2:詳細はUCSR0Cのビット1,2部で紹介

別途記載

1

RXB8

受信データビット8:9bitのシリアル通信を行う場合は受信データの9bit目をここから読み出します。なお、UDR0より先に読み出す必要があります

0

0

TXB8

送信データビット8: 9bitのシリアル通信を行う場合は送信データの9bit目をここから書き込みます。なお、UDR0より先に書きこむ必要があります

0

 

UCSR0C

ビット

項目名

設定内容

設定値(書き込み)

7

 

UMSEL1

USART動作選択:シリアル通信の動作を「非同期動作」「同期動作」「主装置SPI」から選択します。通常は「非同期動作」を選択します。

非同期動作:0,0
(UMSEL1,0)

同期動作:0,1

主装置SPI:1,1

6

UMSEL0

5

UPM1

 

バリティー選択:バリティーチェックを「無効」「偶数バリティー」「奇数バリティー」から選択します。Arduinoなどと通信する場合は「無効」を選択します

無効:0,0 (UPM1,0)

偶数:1,0

奇数:1,1

4

UPM0

3

USBS

停止ビット選択:送信時に送られる停止ビットの長さを「1ビット」か「2ビット」から選択します。通常は、「1ビット」を選びます。

1ビット:0

2ビット:1

2

UCSZ1

 

 

 

データビット長選択:シリアル通信のデータのビット長の選択を行います。「5ビット」から「9ビット」で選択できますが、通常は「8ビット」を選択します

5ビット:0,0,0
UCSZ2,1,0

6ビット:0,0,1

7ビット:0,1,0

8ビット:0,1,1

9ビット:1,1,1(複数プロセッサ動作許可を有効時選択)

1

UCSZ0




0

UCPOL

クロック極性選択:同期動作の時にデータの送受信時のクロックの状態を選択します。非同期動作では0を書き込みます。

非同期動作:0

同期動作

送信時上昇端,受信時下降端:0

送信時下降端,受信時上昇端:1

※ビット2,1は主装置SPI動作の時は設定項目自体が変わります。設定項目名がビット2:UDORD(データ順選択)、ビット1:UCPHA:クロック位相選択に変わります。詳細はここでは省略します。

 

以上がシリアル通信(USART)の設定レジスタです。続いて、通信速度の設定レジスタの紹介をします。通信速度の設定レジスタは「UBRR0です。このレジスタに値を設定することで通信速度が決定されます。このレジスタに入力する値はデータシートP136~P137のボートレート設定例の表から検索するか、データシートP126の数式を変換した以下の式で求めます。

13

 

この検索、計算した値を「UBRR0」レジスタに入力することで通信速度が設定できます。

 

最後にデータの送受信の方法ですが、やり方として2種類あり割り込みを使う方法と使わない方法に分けられます。

前者の割り込みを使う方法の場合は、設定レジスタで割り込みを使う設定(割り込み許可と完了フラグを1)にし、割り込みの関数内で受信時は「UDR0レジスタから読み取り」送信時は「UDR0レジスタに書き込み」を行います。

割り込みを使わない場合は、main関数の無限ループ内で各完了フラグが1に設定されているときに受信時は「UDR0レジスタから読み取り」送信時は「UDR0レジスタに書き込み」を行います。

 

サンプルプログラム

今回は割り込みを使った場合で、Arduinoからデータを受信するようにした場合のプログラムとなります。なお、ここでの設定した内容は、上の設定レジスタの表の設定値の太線の値となっています。

#include <avr/io.h>

#define F_CPU 16000000 //CPUクロック16Mhz

 

#define SPEED 38400//シリアル通信の速度

 

 

int main(void)

{

       

         //シリアル通信設定

        UBRR0 =  ((long)F_CPU/((long)SPEED * 16)) - 1;

 

        UCSR0A = 0x80;

        UCSR0B = 0x98;

        UCSR0C = 0x06;

       

        sei();

       

    /* Replace with your application code */

    while (1)

    {

               

    }

}

 

 

ISR(USART_RX_vect){//シリアル受信完了時

        uint8_t data = 0; //受信データ

        data = UDR0;

}

 

以上がサンプルプログラムとなります。今回はArduinoからのデータ受信を目的にプログラミングを行いました。今回まででモータを鳴らすための回路、プログラム設計に必要な基礎のお話はほぼ紹介したので、次回からは全体的な設計のお話を少ししたいと思います。そのあとまた気分次第でモータを鳴らすことにはつかない基礎的なお話もするかもしれないです。

制御編 第12回 AVRマイコンの割り込みのまとめ

前回まででタイマー割り込みの使い方について紹介してきました。今回は割り込みの種類とそれぞれに使うISR関数の引数(マクロ)を紹介したいと思います。

 Atmega系のマイコンのISR関数の引数はC:\Program Files (x86)\Atmel\Studio\7.0\packs\atmel\ATmega_DFP\1.2.150\include\avrのフォルダ内のiom****.h」(****にはマイコンの型番の数字が入る)のヘッダーファイルの下部に書かれています。それぞれ、「*****_vect(*****は割り込みごとに異なる)の形で表します。(それ以外の形で書かれているのは別物です)Atmega328Pの場合は「C:\Program Files (x86)\Atmel\Studio\7.0\packs\atmel\ATmega_DFP\1.2.150\include\avr\iom328p.h」になります。(Atmega以外のマイコンの場合、上記のパスの「atmel」フォルダに戻り、それぞれのマイコンの種類を選択して同じように進んでください。(attiny2313の場合「C:\Program Files (x86)\Atmel\Studio\7.0\packs\atmel\ATtiny_DFP\1.3.147\include\avr\ iotn2313.h」です。))今回は例として「atmega328P」の割り込みの種類とマクロ名を紹介します。

 

タイマー割り込み

割り込みの種類

マクロ名

タイマー0溢れ割り込み

TIMER0_OVF_vect

タイマー0比較A割り込み

TIMER0_COMPA_vect

タイマー0比較B割り込み

TIMER0_COMPB_vect

タイマー1溢れ割り込み

TIMER1_OVF_vect

タイマー1比較A割り込み

TIMER1_COMPA_vect

タイマー1比較B割り込み

TIMER1_COMPB_vect

タイマー1捕獲割り込み

TIMER1_CAPT_vect

タイマー2溢れ割り込み

TIMER2_OVF_vect

タイマー2比較A割り込み

TIMER2_COMPA_vect

タイマー2比較B割り込み

TIMER2_COMPB_vect

 

外部割込み

割り込みの種類

マクロ名

外部割込み1

INT0_vect

外部割込み2

INT1_vect

ピン変化割り込み0

PCINT0_vect

ピン変化割り込み1

PCINT1_vect

ピン変化割り込み2

PCINT2_vect

以上の2種類(タイマー、外部)の割り込みがマイコンの割り込みの代名詞ともいえるよく使われる割り込みとなっています。外部割込みの設定はatmega328Pの場合データシート(https://avr.jp/user/DS/PDF/mega328P.pdf )P50~53にかけて書かれています。軽くまとめておくと以下のようになります。

外部割込み

EICRAレジスタ:割り込みの条件(ピンの状態がどのように変化した時に割り込みを行うか)の設定

EIMSKレジスタ:外部割込みを許可するレジスタ

外部割込みは以上の2つのレジスタを設定して使います。EIFRレジスタは通常使いません。

ピン変化割り込み

PCICRレジスタ:ピン変化割り込みを許可するレジスタ

PCMSK0レジスタ:ピン変化割り込み0群のうち、変化を読み取るピンを選択するレジスタ

PCMSK1レジスタ:ピン変化割り込み1群のうち、変化を読み取るピンを選択するレジスタ

PCMSK2レジスタ:ピン変化割り込み2群のうち、変化を読み取るピンを選択するレジスタ

ピン変化割り込みは以上の4つのレジスタを設定して使います。PCIFRレジスタは通常使いません。

続いて、通信系の割り込みを紹介します。

通信系割り込み

割り込みの種類

マクロ名

シリアル通信受信完了割り込み

USART_RX_vect

シリアル通信送信完了割り込み

USART_TX_vect

シリアル通信データレジスタ空割り込み

USART_UDRE_vect

SPI通信転送完了割り込み

SPI_STC_vect

TWI(I2C)動作完了割り込み

TWI_vect

以上が通信系の割り込みとなります。「データレジスタ空割り込み」を除いて、通信の動作が完了し次のデータを送受信できる状態になった時がトリガーとなります。この割り込みを使うことでデータを受信したら、そのデータを読み込むといった動作が可能になります。なお、次回の記事でシリアル通信の使い方について紹介します。

 

その他割り込み

割り込みの種類

マクロ名

A/D変換完了割り込み

ADC_vect

アナログ比較器出力遷移

ANALOG_COMP_vect

ウォッチドッグ計時完了割り込み

WDT_vect

EEPROM操作可

EE_READY_vect

SPM命令操作可

SPM_READY_vect

最後のその他の割り込みは使う機会はあまりないと思います。使うとしてもA/D変換完了割り込みぐらいだと思います。

 

以上がatmega328Pにおける割り込みの種類とそれに対応するマクロです。マイコンの種類が変わっても、同様に考えれば割り込み処理は使えると思います。次回はatmega328Pにおけるシリアル通信の使い方を紹介します。



制御編 第11回 マイコンのタイマー割り込み その2

文字数の関係の記事を2つに分けています。今回は前回の続きです。

サンプルプログラム

 今回のサンプルプログラムは、「frequency」変数に入力した周波数(パルス)数でステッピングモータを回転させるプログラムです。回転数に応じてduty比を変化させており、2相励磁となっています。

 

#include <avr/io.h>

#include <avr/interrupt.h>//割り込みのライブラリ

 

void setSquareWave(int freq); //矩形波の周波数設定

 

volatile uint8_t vect = 0;//角度

volatile uint8_t duty_h = 0;//duty125以上

volatile int frequency = 500;

 

volatile double duty = 0;

uint8_t PD[5] = {0x08, 0x20, 0x00, 0x40, 0x08};//ステッピングモータ励磁状態の配列

uint8_t PB[5] = {0x00, 0x00, 0x08, 0x00, 0x00};//この値をPORTに入れる

 

int main(void)

{

         DDRD = 0x68;//PWMピンを出力設定

         DDRB = 0x0F;

               

         //矩形波生成タイマー設定

         TCCR1A = 0x03; //PWM出力なし位相/周波数基準 OCR1A max

         TCCR1B = 0x1B; // 64分周

         TIMSK1 = 0x00; //割り込み停止

         setSquareWave(frequency);//周波数セット

         sei();//割り込み開始

       

    while (1)

    {

    }

}

 

void setSquareWave(int freq) {

  if (freq < 500) duty = 80; //duty比設定//90

  else if (freq < 1000) duty = 120;//120

  else if (freq < 1500) duty = 190;//150

  else duty = 230;//200

 

  if (freq != 0) { //動作

    TIMSK1 = 0x00;  //割り込み禁止

    //TCCR1B = 0x1B;//64分周 /2 250,000回カウント

    OCR1A = (unsigned int)(250000 / freq) ; //割り込み周波数

        if(duty <= 125){ //duty比が低い時(実質1相励磁)

                OCR1B = OCR1A * (duty / 125.0);

                duty_h = 0;//duty

        }else {//duty比が高い時(2相励磁)

                OCR1B = OCR1A * ((duty -125.0) / 125.0);

                duty_h = 1;//duty

        }

   

    TIMSK1 = 0x05;//OVFと比較Bの割り込み許可

   

    DDRC = 0x00;//出力オフ(Hi-Z)

 

  } else  { //停止

    TIMSK1 = 0x00;  //割り込みなし

    PORTD &= ~0x68;

    PORTB &= ~0x08;//出力L

    DDRC = 0x0F;//出力ダウン

  }

}

 

ISR(TIMER1_OVF_vect) { //オーバーフロー割り込み

        vect++; //進める

        if (vect > 4) vect = 1; //OVF

        PORTD |= PD[vect];

        PORTB |= PB[vect]; //出力

 

}

 

ISR(TIMER1_COMPB_vect) { //矩形波割り込み

        if(duty_h){

                PORTD &= ~PD[vect - 1];//1つ前の出力OFF

                PORTB &= ~PB[vect - 1];//1相励磁

        }else{

                PORTD &= ~PD[vect];//出力OFF

                PORTB &= ~PB[vect];//1相励磁

        }

       

}

 基本的なレジスタ設定はPWM出力に準じています。また、ステッピングモータのduty比の設定は溢れ割り込みと比較一致割り込みを組み合わせることで実現しています。ステッピングモータの励磁状態は配列に入れた値を使うことで処理の簡略化を実現しています。

そして、割り込みを使用するにあたって注意しなければならないのが、割り込み用のライブラリである<avr/interrupt.h>を読み込まなければならないことと、割り込み処理はISR(*****)」の関数を使う必要があるということです。また、ISR関数の引数(*****)(マクロともいう)は割り込みの種類ごとに異なるので注意しなければなりません。Atmega328Pにおけるタイマー割り込みの場合はISR関数の引数は以下の表のようになります。

8

割り込み処理の内容はISR関数に上の表の引数を入れること(サンプルプログラム参照)で実行ができます。ここで引数を間違うと割り込み処理ができないので注意が必要です。(コンパイルが通る場合もあるので注意)

以上でタイマー割り込みの設定は終わりです。次回は割り込み処理の引数(マクロ名)の探し方を紹介したいと思います。

制御編 第10回 マイコンのタイマー割り込み その1

前回はマイコンからPWMを出力するお話をしましたが、今回はタイマー割り込みのお話です。前回のPWMはマイコンのタイマー機能を利用していましたが、今回のタイマー割り込みもタイマーを使っています。とはいえ「割り込みってなんやねん」って思う方もいると思うので最初に軽く割り込みについて紹介します。

 

割り込みとは

 割り込みは、設定したトリガが発生した時に強制的に特定の処理をさせることを意味しています。トリガが発生した時にたとえ他の処理を行っていたとしても、割り込みに指定された処理を強制的に実行するため、トリガ発生時に確実な処理が可能になるというわけです。これを図示すると下の図のようになります。
1

 図からも、通常の処理を行っているときにトリガが入ると強制的に割り込み処理を開始し、割り込み処理が終了するまでは他の処理が中断されることがわかりますね。(Atmega328Pなどの1スレッドのマイコンの場合)

 

タイマー割り込みとは

タイマー割り込みでは、このトリガが「タイマー」によって発生します。つまり、一定の時間ごとに割り込み処理を行えるということです。ステッピングモータを回すパルスを発生させるときなど正確な時間間隔で似た処理を行いたいときなどに非常に使い勝手の良い処理と言えますね。それでは、続いてAtmega328Pにおけるタイマー割り込みの原理を紹介していきます。

タイマー割り込みは前回紹介したPWMと同様にマイコンのタイマー機能を使っており、マイコンのタイマーのカウンタがある状態になった時に、トリガを発生させるという仕組みです。ここでの、ある状態とは、タイマーがカウントの数がオーバーフロー(溢れ)した時比較値PWM生成の時の閾値 OCR0Bなど)と一致した時などを指します。つまり、タイマーのカウントが特定の値に達した時に、割り込み処理が実行されるというわけです。このタイミングを図示してみます。
2

 上の図がタイマーのカウンタがオーバーフローしたときの割り込み(マイコンではこれを溢れ割り込みと言います)を示しており、タイマーのカウンタがオーバーフローする↓で示したタイミングで割り込み処理が行われます。ただし、この溢れ割り込みのタイミングはタイマーの動作種別によりタイミングが変わります。(詳細は次の節)また、同様に下の図はタイマーのカウンタが比較Aに達した時の割り込み(比較A割り込み)を示しており、↓で示したタイミングで割り込み処理が実行されます。

 

タイマー割り込みの注意点

 先ほどの節でタイマー割り込みの原理を紹介しましたが実際にタイマー割り込みを使用するにあたって注意しなければならない点が複数あります。1つ目は溢れ割り込みのタイミングで2つ目は比較・捕獲割り込みのタイミング、3つ目はTOP値と比較・捕獲値の関係です。

溢れ割り込みのタイミング

 溢れ割り込みは定義上はカウンタの値がオーバーフローした時の割り込みです。しかし、溢れ割り込みでオーバーフローした時に割り込みが入るのはデータシート上「標準動作」と「比較一致タイマ/カウンタ解除(CTC)動作」のみであるということに注意しなければなりません。すなわちよく使われる、「高速PWM動作」「位相基準PWM動作」、「位相/周波数基準PWM動作」においてはタイマーのカウンタがオーバーフローした時に割り込みが実行されないというわけです。Atmega328pのデータシート(https://avr.jp/user/DS/PDF/mega328P.pdf )P.99(タイマー1)を確認すると、このことの記載がされており、タイマーの動作種別によりタイミングが異なると書かれています。そのタイミングはタイマー動作種別の表(以下に引用)に書かれています。
3
4

上が8ビットタイマーで下が16ビットタイマーの場合です。それぞれの表の右端の赤で囲んだ「TOV1設定時」の項目に溢れ割り込みが入るタイミングが書かれています。これより「高速PWM動作」ではタイマーのカウンタが「TOP値」に達した時、「位相基準PWM動作」「位相/周波数基準PWM動作」ではタイマーのカウンタが「BOTTOM(0)に達した時に割り込みが動作することがわかります。これを図示してみます。
4-1
 図を見れば「高速PWM動作」はTOP値で割り込みが入っていますが、TOP値は同時にBOTTOM値でもあるのでどちらかというと溢れ割り込みは、タイマーのカウンタが0になったタイミングで入ると考えても問題はないでしょう。以上が溢れ割り込みの動作のタイミングの注意点です。

 

比較・捕獲割り込みのタイミング

 比較A,B割り込みや捕獲割り込みはタイマーのカウンタの値が比較A,B(OCR*A,B)や捕獲値(ICR1)と一致した時をトリガとして割り込みが入ります。原理上はこれだけなのですが、タイマーの動作種別により割り込みが入るタイミングや回数が変わることに注意しなければなりません。割り込みが入るタイミングを図示してみます。
4-2
 この図を見れば、高速PWM動作ではカウンタ1周期の間に割り込みが1回で等間隔に割り込みが入るのに対して、位相(/周波数)基準PWM動作ではカウンタ1周期の間に2回割り込みが入り割り込みが入るタイミングが等間隔でないことがわかります。割り込み動作の時はカウンタが上昇時と下降時の区別がされずに単に値が一致した時がトリガとなるためこのような動作となります。

 

TOP値と比較値との関係

 この関係はタイマーのカウンタのTOP値と比較値が一定の関係にならないと割り込みが入らないことを示しています。割り込みが正しく入る条件はこのような関係です。

比較値≦TOP

この関係も原理から考えると非常にやさしいものですが2つの値の関係よってどのようになるかを図示してみます。
5

比較値とTOP値の関係を「比較値>TOP値」「比較値=TOP値」「比較値<TOP値」の3つの場合を図示してみました。この図を見れば、「比較値>TOP値」の時に割り込みが一切入らなくなることは明らかですね。また、「比較値=TOP値」はカウンタがTOP値に達した時に割り込みが入るため、カウンタ1周期につき1しか割り込みが入らないこともわかると思います。

 

以上の3つが割り込みを使うときの大きな注意点となります。続いて、各種レジスタの設定方法ですが、波の形と最大高さ関連の設定は前回のPWMの記事http://vvvf.blog.jp/archives/7662206.html)を参考に行ってください。ここではタイマー割り込みにおいてPWM出力時と異なるレジスタ設定の項目を紹介します。

 

割り込みのレジスタ設定

 6

初めに、各出力ポートの設定です。今回はPWM出力を利用しないので、すべて「標準ポート動作」(COM*x: 0 0)を選択します。TCCR0A,BTCCR1A,BTCCR2A,Bのその他の設定はPWMの時と同じようにしてください。

続いて、割り込み動作の許可の設定です。
7

この割り込み許可レジスタで、各種割り込みを許可する設定を行っています。利用する割り込みのビットを立てることで、割り込みが使用できるようになります。なお、捕獲割り込みは「ICR1」と一致時、比較一致割り込みは「OCR*A」「OCR*B」と一致した時に割り込みが入ります。

以上でレジスタの設定は終了ですが、割り込みを使用するにはプログラムですこし変わった書き方が必要となります。各レジスタの実際の設定値と合わせて、次のサンプルプログラムのところで紹介します。

 

文字数の関係でサンプルプログラムはその2に続きます

制御編 第9回 マイコンのPWM出力 その2

前回は任意の周波数のPWMを生成するためのパラメータを決定しました。今回はその決定したパラメータを実際にプログラムに落とし込む方法を紹介します。

レジスタの設定

いままでの計算で目的の周波数のPWMを出力する設定は見つかりました。今度は実際にその設定をレジスタに入力していきます。設定情報は毎度のごとくAtmega328Pのデータシート(https://avr.jp/user/DS/PDF/mega328P.pdf )から探します。タイマー0のレジスタ設定はP76~78、タイマー1のレジスタ設定はP94~96、タイマー2のレジスタ設定はP112~114に書かれています。なお、8ビットタイマーの「タイマー0,1」と16ビットタイマーの「タイマー1」は分けて紹介したいと思います。

 

8ビットタイマー

8ビットタイマーは「タイマー0」と「タイマー2」に搭載されていますがほぼ設定内容は同一なので「タイマー0」を基準に紹介していきます。(分周のところだけ違うのでそこだけ別途説明します)タイマー0における、PWM出力の設定レジスタは「TCCR0A」と「TCCR0Bです。また、可変周波数PWMを行う場合の波の高さは「OCR0Aレジスタに、duty比を設定する閾値は「OCR0Bレジスタに入力します。それでは、各レジスタの中身を見ていきましょう。

1
2
3
4

PWMを生成するのに使用するレジスタはこの4つですが、下の2つのレジスタは数値を入力するだけなので、詳細な説明は省略します。上の2つのレジスタは各ビットの状態を使ってさまざまな、設定を行っています。それぞれの設定項目別に紹介をしていきます。

OC0Aの端子の出力設定(比較A出力選択)TCCR0Aの第6,7ビット」
5

この項目で確認する必要があるのは、図の赤で囲んだところです。PWMの動作種別(波の形)によって「高速PWM比較A出力選択」か「位相基準PWM動作比較A出力選択」を確認して下さい。波の高さを可変する可変周波数PWMを出力する場合は、ここで出力設定をするOC0A」端子に対応する、比較レジスタ(PWM生成の閾値)が波の高さを設定するレジスタそのものであるため、この端子からPWMを出力することはできません。そのため、可変周波数PWMを使用する場合は図の赤で示した「標準ポート動作(OC0A切断)」を選択します。つまり、COM0A1,COM0A0ともに0となります。

可変周波数PWMを使わず、限られた周波数のPWMのみを出力する場合は「OC0A端子もPWM出力が可能となり、その場合は図の青←で示した「比較一致でLow…」「上昇計数時の比較一致でLow…」の設定を選択します。

 

OC0Bの端子の出力設定(比較B出力選択) TCCR0Aの第4,5ビット」

 6

この項目の中身自体は先ほどの設定と大差なく、確認する箇所も同じですが、可変周波数PWMを使用する場合は設定する内容が違うことに注意が必要です。ここで設定を行う「OC0B」端子は、可変周波数PWMを行う場合ではPWMが出力される端子です。そのため、図の赤←で示した「比較一致でLow…」「上昇計数時の比較一致でLow…」、つまり「COM0B1:1, COM0B2:0」を選択しなければなりません(データシートのレジスタの項目名は間違いです)この設定をしないとPWMが正しく出力されないので注意が必要です。(なお、COM0A1,2:11を選ぶと出力波形が反転します)

 

タイマー波形選択TCCR0Aの第0,1ビット、TCCR0Bの第4ビット」
7

ここの項目ではタイマーで生成される波形の種類を選択します。可変周波数のPWMを生成する場合は波の高さを変更できるようにする必要があるため、TOP値をレジスタで設定できる項目を選ばなければなりません。そのため、図の赤の←で示したTOP値が「OCR0A」で定義できる設定を選択します。波形が三角波の「位相基準PWM動作」を選択する場合赤←1で示した「TOP:OCR0Aの位相基準PWM動作」(WGM02:1 WGM01:0 WGM00:1)を選択します。また、波形がのこぎり波の「高速PWM動作」を選択する場合赤←2で示した「TOP:OCR0Aの高速PWM動作」(WGM02:1 WGM01:1 WGM00:1)を選択します。

可変周波数のPWMを使用しない場合は図の青←で示したTOP値が「$FF(255)」の項目を選択します。

 また、この設定項目を保存するレジスタはWGM01WGM00が「TCCR0A」、WGM02が「TCCR0B」と2つに分かれていることに注意しなければなりません。

 

分周選択
8

この項目では、タイマーに使う分周の値を選択します。必要な分周に応じた項目を選択してください。ただし、この項目は同じ8ビットタイマーでも「タイマー0」と「タイマー2」で選択肢が異なるので注意してください。上の画像は「タイマー0」の設定です。「タイマー2」の場合は以下のような選択肢に変わります。
9

このように、同じ分周数でもレジスタに設定する値が変わってくるので注意が必要です。

 

レジスタの設定例

これまでに説明した、手順で実際に任意の周波数のPWMを出力する設定を紹介します。

今回はシステムクロック16Mhzで出力PWM1kHzとし、タイマーの波形は位相基準PWMとした場合です。また、duty比は0.5としておきます。

初めに、分周値とTOP値を選定します。(分周は64として代入)
式

計算結果より、TOP値は125となり使用可能範囲であるため、設定パラメータは分周64TOP125とします。

各レジスタに設定した値を入力していくと以下のようなパラメータとなります。なお、赤字は関係ないor設定できない項目なので0にしています。
10
11
TOP値を定義する「OCR0A」とduty比を設定する閾値を定義する「OCR0B」には以下のように値をそのまま入力させます。

OCR0A=125

OCR0B=OCR0A*0.5=62

このように設定することで任意の周波数のPWMを生成することができます。

 

16ビットタイマー

 16ビットタイマー(タイマー1)の場合も8ビットタイマーと同様に設定を行っていきます。データシートのP94~96より、PWMの設定レジスタは「TCCR1A」と「TCCR0B」となっています。そして、可変周波数PWMを使う場合の波の最大値は「OCR1AorICR1」、PWM生成の閾値は「OCR1B」となっています。後者の3つのレジスタは値を設定するレジスタです。16ビットタイマーなので設定できる値は16ビットとなりますが、あくまでAtmega328P8ビットマイコンなので8ビットのレジスタを2つ使う形となっています。

 8ビットタイマーとシステムが近いので違いがある点を絞って紹介をしていきたいと思います。まずは、設定レジスタである「TCCR1A」と「TCCR1B」の中身を見てみます。

 12
13

設定レジスタの中身はこのようになっており、タイマーの波形選択である「WGMn」以外の設定は8ビットタイマーと同じ設定です。今回は8ビットタイマーと設定が異なる波形の形の設定を見てみましょう。

タイマー波形の選択TCCR1Aの第0,1ビット TCCR1Bの第3,4ビット」
14

8ビットタイマーに比べて設定項目が非常に多くなっています。8ビットタイマーとの違いは主にTOP値の選択項目が多くなっているということで、0x00FF」「0x01FF」「0x3FF」「OCR1A」「ICR1」と5種類のなかから選ぶことが可能になります。可変周波数PWMを使う場合は赤色の←示した「TOPOCR1Aの位相/周波数基準PWM動作」(WGM1: 1 0 0 1)TOPOCR1Aの高速PWM動作」(WGM0: 1 1 1 1)を選択すると良いでしょう。また、可変周波数PWMを使わない場合でほかの8ビットタイマーと同様に使いたい場合は青←で示した「TOP0x00FF8ビット位相基準PWM動作」(WGM1: 0 0 0 1)や「TOP0x00FF8ビット高速PWM動作」(WGM1: 0 1 0 1)を選択すると良いでしょう。

16ビットタイマーでもほかの設定は8ビットタイマーと同じなので省略します。次に波の高さ(TOP値)やPWMを生成する閾値を設定するレジスタを見てみます。
15

Atmega328Pはあくまで8ビットマイコンなので16ビットの値を1つのレジスタとして扱うことができません。そのため、内部的には下位バイトと上位バイトの2つのレジスタに分けられていますが、Atmel Studioではこれら2つをまとめて「OCR1A」として扱うことが可能になっています。そのため、ほかの8ビットタイマーなどと同様の感覚で使うことが可能です。(この書き方ができない環境の場合はビット演算とビットシフトを使って、それぞれのレジスタに書き込みをする必要があります)

 

レジスタの設定は、8ビットタイマーの時と同様に行います。

 

サンプルプログラム

今回は、ある変数に入力された周波数のPWMを生成するプログラムをサンプルとして紹介したいと思います。今回はタイマー0を使用しDuty比は31%に固定としています。

#include <avr/io.h>

 

volatile int freq = 1000; //出力周波数

volatile double duty = 80; // 80/2550.31331%

 

int main(void)

{

         DDRD = 0x68;//PWMピンを出力設定

         DDRB = 0x08;

         //矩形波生成タイマー設定

         TCCR0A = 0x01; //PWM出力停止 位相基準 OCR0A max

         TCCR0B = 0x0C; // 256分周

 

TCCR0A &= ~0x20;//PWM停止

if((freq > 125) && (freq < 2000)){

                TCCR0B = 0x0C;//256分周

                OCR0A = (unsigned int)(31250 / freq) ; //割り込み周波数

                OCR0B = OCR0A * (duty / 250.0);

                TCCR0A |= 0x20;//PWM起動 OCR0Bのみ

}else if((freq > 50) && (freq < 125)){

                TCCR0B = 0x0D;//1024分周

                OCR0A = (unsigned int)(7812 / freq) ; //割り込み周波数

                OCR0B = OCR0A * (duty / 250.0);

                TCCR0A |= 0x20;//PWM起動 OCR0Bのみ

}else if((freq > 2000) && (freq < 7000)){

                TCCR0B = 0x0B;//64分周

                OCR0A = (unsigned int)(125000 / freq) ; //割り込み周波数

                OCR0B = OCR0A * (duty / 250.0);

                TCCR0A |= 0x20;//PWM起動 OCR0Bのみ

}

 

    while (1)

    {

}

 

プログラムとしては、TOP値が8ビットに収まる範囲で場合分けをして分周を区切っています。また、プログラム実行中の周波数を変更も視野に入れたため、念のためPWM周波数を変更する処理を行うときはPWM出力を一旦停止させています。TOP値の計算は前回紹介したものをあらかじめ計算可能な場所のみ計算したものを使用しています。

 

以上で任意の周波数のPWMを出力する方法の紹介は終わりです。次はタイマー割り込みのお話を予定しています。


制御編 第8回 マイコンのPWM出力 その1 可変周波数PWMの理論とパラメータ決定

前回はデジタル入出力のお話をしましたが、今回は疑似アナログ出力ともいわれるPWMの出力を紹介します。PWMと言ってもなにかわからないかもしれないので、まずは、PWMがどんなものであるかをお見せしましょう。
1
この図の波形は電圧の波形を示しています。PWMというのはこのようにONOFFを高速で繰り返して、疑似的にアナログ出力を行おうとするものです。出力電圧は以下の式で表せます。

Vin:入力電圧[V]

Vout:出力電圧[V]

T:PWM周期[s]

t:ON時間[s]

Vout=Vin×(t/T)

つまり、ONの時間とOFFの時間の割合を調整して、0Vから入力電圧の範囲でいろいろな電圧を作り出せるというわけです。そして、PWM周期とON時間の比をデューティー比と言います。

そして、モータを演奏するうえでは非常に有用なのがPWM周期です。PWM周期が短くなると高い音周波数が高い音)が出て、PWM周期が短くなると低い音周波数が低い音)なります。ちなみに、周期から周波数は以下の式で変換できます。

f:PWM周波数[Hz]

f = 1/T

この式より、PWM周期が大きいとPWM周波数が低くなり低い音が、PWM周期が小さいとPWM周波数が高くなり高い音が出ることがよくわかると思います。つまり、モータ演奏はこのPWM周波数を調整して音色を演奏するというわけです。この章では、PWMの出力方法はもちろん、PWMの周波数の変更方法も紹介していきます。

 

マイコンのタイマーの動作

マイコンのPWM出力はArudinoでいうdelay()のようなものを使って、手動でONOFFを繰り返して出力するものではありません。マイコンのPWMはマイコン内部に搭載されているタイマーと呼ばれる機能を使って、波形を生成しています。

今回使用している「atmega328P」の場合はタイマーが3つ搭載されており、内部の最大カウントが8ビット(255)の8ビットタイマーが2つ、最大カウントが16ビット(65535)16ビットタイマーが1つ搭載されています。それぞれのタイマーで出せる周波数やデューティー比の精度が大幅に変わってきます。(もちろん16ビットタイマーの方が精度を出しやすい)

話ばかりでは、分からないと思うので、実際にマイコンのタイマーがどのようなものであるかをデータシート(https://avr.jp/user/DS/PDF/mega328P.pdf )から見てみましょう。データシートの89ページからタイマーについて書かれているページがあります。

はじめにタイマー全体の概要を紹介するために例として、高速PWM動作を使ってみます。
2

マイコンのタイマーは、内部的に図の→で示したようなのこぎり波を描くカウンタがあります。時間(CPUのクロック数)に応じてこのカウンタの値が変化し、こののこぎり波と閾値を比較してPWMの波形を生成しています。CPUのクロック数に応じてPWMの波形を生成しているため、一定で正確なPWM周波数とデューティー比を出力できるというわけです。PWMは図で示したように、のこぎり波の値が閾値より小さい時ON状態になっていて、閾値より大きい時にOFF状態となってことがわかります。

マイコンでは、この上下する波形の形や、最大高さ傾き変更することができます。これらの変化させることで、生成するPWMの周波数を変更することができます。ここからは、波形の形、最大高さ、傾きによりPWM周波数が変えれることを紹介していきます。

 

波形の形

波形の形は2種類あり、先ほど示したようなカウンタが最大値となると0に戻るのこぎり波を描くタイプと、カウンタが最大値になると値が降下し始める三角波を描くタイプの2種類があります。この2種類の波形は動作種別により設定ができます。前者のタイプの波形は「高速PWM動作」を選んだ場合に生成され、後者のタイプの波形は「位相基準PWM動作」または「位相/周波数基準PWM」を選んだ場合に生成されます。

傾きと最大高さが同じ場合に、PWMの出力波形がどのようになるかを図示してみます。
3

この波形の図からもわかるように、のこぎり波を生成する「高速PWM動作」で生成されるPWM周波数は、三角波を生成する「位相基準動作」「位相/周波数基準動作」(位相/周波数基準動作はタイマー1のみ)で生成されるPWM周波数の約2倍の周波数になっていることがわかります。(約とつけたのは、完全な2倍ではないため)これによって、PWM周波数を2倍に変化させることはできますが、これだけでは音色を奏でることはできません。 そのため、ほかのテクニックを組み合わせる必要があります。

また、データシートの「位相基準動作」のページを確認すると、「位相/周波数基準動作」が使えるタイマー1の場合はTOP(波の最大高さ)を変更する場合は、「位相/周波数基準動作の使用を推奨する」と書かれています。そのため、今回使用する動作種別は「高速PWM動作」と、「位相/周波数基準動作」(タイマー1)or「位相基準動作」(タイマー0,1)をタイマーに応じて使い分け、各波形ごとに1種類の種別を使うことにします。

 

波の最大高さ

先ほどは、波の形を変えて周波数を変化させようとしましたが、今度は波の最大値(高さ)を変化させてPWM周波数を変化させる方法を紹介します。

傾きが同じ場合に最大値を変化させると生成される周波数が変化するというわけですが、言葉ではわかりにくいと思うので、どのようになるのかを図示してみます。
3

この図から、傾きが同じでも波の高さを高くすればPWM周波数は低くなり、高さを低くすればPWM周波数が高くなることがわかります。これより、PWMの周波数は波の高さに反比例することがわかると思います。

 波の最大高さの設定は、PWMの出力の設定によって可否が変わります。周波数を可変するPWMを生成する場合は、最大値が固定ではなくレジスタで設定できるものを選びます。(可変しない場合はTOP値0xFF(255)のものを選ぶ)8ビットタイマーである「タイマー0」と「タイマー2」の場合は波の最大高さはOCR0A」、「OCR2A」のレジスタで設定できます。また、16ビットタイマーである「タイマー1」の場合は波の最大高さを「ICR1」かOCR1Aで設定できますが、PWM周波数を頻繁に変える場合「ICR1」を使うと正しく動作しません。(データシートによると「ICR1」を変更する際に2重緩衝がされないため、条件によって正しく動作なると書かれており、波の高さを変更する場合は「OCR1A」の利用を推奨すると書かれています。筆者の環境で「ICR1」で周波数可変を試しましたが、正しく音が出ないときが発生しました。)そのため、波の高さを変える(周波数を変える)場合に使える波の最大値(TOP値)の設定レジスタは「OCR*Aとなります。

 次に、各タイマーで使える波の高さの最大値について紹介します。設定できる波の高さの最大値は8ビットタイマー(タイマー0,2)と16ビットタイマー(タイマー1)で大きく違うことに注意しなければなりません。 

8ビットタイマーはその名の通り、内部処理を8ビットで行っています。そのため、設定できる波の高さの最大値も8ビットで表せる数に限られます。つまり、最大値は255(2^8-1)というわけです。また、PWMを生成するにあたって波の高さが最低2は必要(最大高さ1以下だと、閾値との比較ができずPWMとして成立しない)です。そのため、原理上設定できる波の高さは2255というわけです。しかし、波の高さが低いとduty比の変更が困難になってくるので、実質的に使用できる波の高さの最低値は20~30程度として、実使用においての波の高さの範囲は30255となります。

16ビットタイマーの場合は内部処理が16ビットで行われているので波の最大高さは65,535となり、実質的に波の高さとして設定できるのは20~65,535となります。そのため、周波数の設定の自由度は圧倒的に16ビットタイマーの方が高いというわけです。

 

波の傾き

 波の形や高さを変えて周波数を変化させる方法以外にも波の傾きを変更して周波数を変更する方法を紹介しましたが、次は波の傾きを変えて周波数を変更する方法を紹介します。先ほどまでの方法に比べると直感的にもわかりやすいとは思いますが、今回も図示してみます。5

 この図から、傾きを小さくするとPWM周波数が低くなり、傾きを大きくするとPWM周波数が高くなることがわかります。これより、PWMの周波数は波の傾きからも変更できることが明らかですね。

 AVRマイコンではタイマーの波の傾きは、マイコンに接続されている水晶発振子の周波数をもとに決められています。ですが、プログラム上からは水晶発振子の周波数を変更することは基本的にできません。そのため、タイマーには分周器という水晶が一定の回数発振したら、カウントを1つ進めるという機能が搭載されています。この分周器の設定を変更することによりタイマーのカウンタの傾きを変更できるというわけです。それでは分周器を使った場合のタイマーの波形のイメージを実際に図示してみましょう。
7

分周のイメージとしては、このような感じのイメージになっています。(実際の動作とはちょっと違うかもしれないけど)つまり、分周をした分だけ傾きが小さくなります。図で示した8分周の場合は傾きが1/8になり、結果として出力される周波数も1/8となります。

分周器で設定できる分周はタイマーごとに決められており、Atmega328Pの場合設定できる分周は以下の通りです。
8

このようにいくつかの決められた分周の中から選択をすることで大まかな、周波数を決めることができます。また、外部からクロックを与えることでPWMを生成することも可能です。(ここでは紹介しません)

 

出力周波数の設計

先ほどまでは、PWMの理論と周波数を変える理論の紹介を行いましたが、次は実際に周波数を変えるためのパラメータの決定を行っていきたいと思います。設計の手順としては

波の形→波の傾き→波の高さ

で設計を行います。

初めに、波の形を決めます。のこぎり波の高速PWM動作三角波の位相基準動作(タイマー0,2)位相/周波数基準操作(タイマー1)のどちらかから選びます。特に理由がなければ三角波を選んでおけばよいでしょう。なお、ここで選ぶ波形によって使用する数式が変わるので注意が必要です。波の形を決めたら、周波数とパラメータの関係の数式からパラメータ選定を行います。データシートから出力周波数は以下の式で表せることがわかります。

9
初めに、この式に変形して以下のようにします。

10
そして、この式に、出力したいPWM周波数とシステムクロック(水晶の周波数)、そして適当な分周の値(8ビットタイマーで可聴周波数の場合は64分周程度を推奨)を代入して、TOP値を計算します。TOP値の計算結果が255(8ビットタイマーであるタイマー0,2),65535(16ビットタイマーであるタイマー1)を超えた場合分周の値を大きくします。逆にTOP値の計算結果が30を下回った場合分周の値を小さくします。分周の値を決めて、この式で計算したTOP値を波の高さとして設定すれば、出力したい周波数のPWMを出力することができます。

PS. 16ビットタイマーの場合でもTOP値が大きくなると扱いにくい場合は、必要に応じて分周の値を大きくしてください。

 

以上で任意の周波数のPWMを生成するための設定のパラメータは求まりました。次回は実際にプログラムに入力する方法を紹介します。

制御編 第7回 AVRマイコンのデジタル入出力

今回からは、実際にマイコンを使って特定の動作を行うプログラミングのお話を行っていきたいと思います。ここでは、レジスタの設定情報はもちろん、サンプルのプログラムも合わせて紹介していきたいと思います。

 

今回はマイコンの基本中の基本であるデジタル入出力のお話を行いたいと思います。

 

入出力設定

マイコンを使うときには初めにマイコンにピンを入力として使うか、出力として使うかを設定しなければなりません。

ピンを入力モードとして設定すると、インピーダンスは高くなります。逆に出力モードとして設定を行うと、ピンのインピーダンスは低くなります。そのため、回路上入力になる端子を出力設定すると、場合によっては過電流が流れマイコンが破損する場合があります。逆に回路上出力の個所を入力設定にすると出力先をドライブできなくなりますが、マイコンが破損する心配はほぼありません。(出力の条件次第(出力先の入力部をプルダウンやプルアップしていないなど)で出力先の回路が壊れる危険性はありますが…)この理由からAVRマイコンでは初期設定では、ピンは入力モードになっています。

レジスタ設定

入出力の設定を行うレジスタはAtmega328のデータシート(https://avr.jp/user/DS/PDF/mega328P.pdf )65ページから67ページにかけて書かれています。これより、PORTB,C,Dそれぞれの入出力を設定するレジスタはDDRB,C,Dであることがわかります。

例としてPORTBの入出力の設定レジスタであるDDRBのリストを引用します。
no title

DDRB」レジスタの0ビット目から7ビット目までそれぞれPB0~PB7までの入出力の設定を行うレジスタビットになっています。それぞれのピンに対応するビットを0にすると入力、1にすると出力になります。初期設定は上の図の「リセット値」になるのですべて0、つまりすべて入力モードになっています。

また、PORTBの場合以下のようにPB6,7(下図赤□)が水晶端子と重なっていることがわかります。1

このように特定の機能を持ったピンがある場合は、そのピンの持つ機能に合わせて入出力を設定します。今回のPB6,7は水晶入力なので、入力の設定にしておきます。(ピンの特殊機能を使う場合は基本的にここでの入出力設定は無視されて、正しい設定になるはずですが、一応保険のためにこのように設定しておきます。)

また、青で囲った部分は通常のピンとして利用できるピンです。このピンの中から使用するピンの入出力の設定を行います。(この中にはSPI通信の機能を持ったピンもあるので必要に応じて使い分けてください)

 

例として、PB1,3を出力、PB0,2,4,5を入力とする場合のレジスタ設定を紹介します。

2

上位の2ビットは使用不可なので0(入力)とし、ほかは出力ピンを1入力ピンを0としてビットを並べて2進数としたものを、16進数に変換を行い桁数の削減を行います。

 

デジタル出力(出力状態設定)/内部プルアップ設定

入出力の設定が終われば次は、マイコンのデジタル出力の設定を行います。これは、マイコンのピンからHレベルの信号を出力するかLレベルの信号を出力するかの設定です。また、ピンを入力モードとしたときに、ピンをマイコン内部でプルアップするかしないかの設定も同じレジスタで行います。

レジスタ設定

ピンの出力状態の設定を行うレジスタはデータシートの6567ページより、PORTB,C,Dそれぞれ、PORTB,C,Dであることがわかります。また、データシートの55ページの以下の情報から、プルアップが可能なことがわかります。
3

図の青で囲った、「PUD(MCUR4bit)(プルアップ禁止)レジスタは初期設定で0、つまりプルアップ許可(禁止でない)となっています。そのため、入力状態(DDRB,C,Dの対応ビットが0)である時に、PORTB,C,Dの対応ビットを1にすると、そのピンがプルアップされた入力になるというわけです。

 

次に例としてPORTBを紹介します。今回もリストを引用します。
4

入出力の設定と同じく、「PORTB」レジスタの0ビット目から7ビット目までそれぞれ、PB0からPB7までのピンの出力状態の設定を行うレジスタとなっております。それぞれのピンに対応するビットを0にするとLが出力され、1にするとHが出力されるようになっています。また、初期時(リセット時)はすべてLが出力されます。

 

例として、PB2,3H出力、PB4,5L出力、PB1をプルアップ入力、PB0をハイインピーダンス入力とした場合のレジスタ設定を紹介します。

まずは入出力の設定

DDRB 0b00111100

DDRB = 0x3C

次に出力状態設定
5

入出力設定は、上位2ビットが使用不可、下位2ビットが入力で、ほかは出力なので、その通りにビットを並べ、入出力の設定を行います。そして、出力状態の設定はPB2,3H出力、つまり2,3ビットが1、またPB1がプルアップなので1ビットが1になり、ほかはLまたはHZ(ハイインピーダンス)なので0となります。こちらも、最終的には桁数削減のために16進数表記にしています。

 

デジタル入力(ピン状態読み込み)

先ほどの設定でピンの出力状態の設定はできました。ですが、出力のレジスタでは入力モードの時にピンの状態を読み込むことができません。そのため、入力状態を取得するときには「入力レジスタ」と言われる別のレジスタを使用します。

レジスタ設定

ピンの入力状態を読み込むレジスタは、先ほどと同じくデータシートの65~67ページより、PORTB,C,Dのそれぞれに対して、PINB,C,Dであることがわかります。例として、PORTBの入力レジスタである、PINBについて紹介します。
6

このレジスタも、0ビット目から7ビット目までそれぞれPB0~PB7に対応しており、1を読み込んだ時はピンの状態がHレベル、0を読み込んだ時はピンの状態がLレベルとなっております。

また、このレジスタは種別としては読み書きの両対応のレジスタになっていますが、基本的には読み込みとして使用します。(PINBに書き込みを行うと、対応するPORTBの状態がトグルしますが、使うことはないと思います)

例として、PB3の状態を読み込む方法を紹介します。
7

特定のピンのみの状態を読み込む場合はAND演算を使って、読み込みたいビットのみを取り出します。(ほかのピンの状態は無視する必要があるため)また、必要に応じてビットシフトを行って、ビットをずらします。

 

以上で今回の(デジタル)入出力のお話は終了です。

 

サンプルプログラム

今回紹介したデジタル入出力を使った、サンプルプログラムの紹介を行いたいと思います。今回は、PD7にアクティブLOWのスイッチを接続し、内部プルアップを設定します。また、PB5端子にアクティブHIGHのLEDを接続し、スイッチが押されている間点灯するプログラムとします。

 

List1 DigitalInOut

#include <avr/io.h>

int main(void)

{

  DDRB = 0x20;//PBを出力

  DDRD = 0x00;//PD7は入力

  PORTD = 0x80;//PD7をプルアップ

  

    /* Replace with your application code */

    while (1)

    {

    if(PIND & 0x80){//H状態=ボタンが押されていないとき

      PORTB &= ~0x20;//PB5L出力

    }else{//L状態=ボタンが押されているとき

      PORTB |= 0x20;  //PB5H出力

    }

  }

}

 

プログラムの構成としては、初めに初期設定として、入出力の設定とプルアップの設定を行います。その後の無限ループ内で、PD7の状態を読み込み、Hの場合はPB5L状態、Lの場合はPB5H状態にしています。また、if文は0か非0かの判定であるので、状態判別時のビットシフトは省略できます。

また、今回の条件の場合は別の書き方として、while(1)の中を下のようにもすることができます。

while (1)

{

  PORTB = ~((PIND & 0x80) >> 2);

}


こちらの書き方の考え方は、入力信号をANDとビットシフト、反転を行い、if文などを使わずに、そのまま出力しています。

この2つの書き方は場合により使い分ければよいと思いますが、後者は比較的使う場面は少ないと思います。(マイコン的な考え方ではありますが…)まあ、考え方として知っておけば損はしないと思います。

 

今回はマイコンの入出力の方法を紹介しました。次回はマイコンで音楽を奏でるミソであるPWMの出力のお話をしていきたいと思います。

ギャラリー
  • HDDを演奏する楽器を作ってみた
  • HDDを演奏する楽器を作ってみた
  • HDDを演奏する楽器を作ってみた
  • HDDを演奏する楽器を作ってみた
  • 制作日記5 細かな基板を注文してみた
  • 制作日記5 細かな基板を注文してみた
  • 制作日記5 細かな基板を注文してみた
  • 制作日記5 細かな基板を注文してみた
  • 制作日記5 細かな基板を注文してみた
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計: