公開日:2024/03/23
title.png

29th Season


#29-7 小さなソフトウェアシリアル

 最近入手した、8ピンDIP形状の小さなマイコンATtiny85(下図)は、電源、GND、リセット端子を確保すると、残りたったの5端子と言う、とても小さなMCUです。

a293_attiny85.jpg

 残り5本のI/Oポートから、Arduino IDEで使用可能なシリアルモニタ(UART)を使おうとすると、双方向の通信線(TXD,RXD)が2本も占有されてしまって、残り3本……。実質「使えない」、なんてことになりかねません。

 今回は、そんなATtiny85と、非同期シリアル(UART)通信がテーマです。

シリアルモニタ

 シリアルモニタはArduino IDEに備わっていて、USB経由で非同期シリアル(UART)通信して、Arduino UNO等のマイコンボードと通信することができる機能です。
 プログラムやシステム全体のデバッグをする際に処理の途中経過をPCへ送信し、結果を確認しながらデバッグしたり、こちらからコマンドを投げて処理タイミングを制御したり、デバッグの強い見方であります。

 マイコンのUART通信としてみると、デバッグ以外にも何かと使い道はあります。

 UART通信は、送信のみ/受信のみの単方向通信としても、送受信可能な双方向通信としても使える上に、通信信号線は1方向当たり1本で済むので、コストの小さな通信仕様でもあります。

ATtiny85のI/Oポート

 まず、こちらを参照してATtiny85の仕様を復習してみてください。ATtiny85には、UART通信可能なハードウェアUARTユニットは非搭載です。
 正確には、UART通信を実行可能なUSIユニットを搭載しているのですが、このユニットは基本的に、I2C通信やSPI通信を実行するためのユニットなので、UART通信として使おうとすると「半二重」通信あるいは「単方向(一重)」通信としてしか使用できません。
 当然、I2C通信やSPI通信とリソース競合するため、これらの通信と同時に使う事もできないわけです。

 次に、こちらを参照して、ATtiny85のピン配置を確認して見てください。
 ATtiny85の各I/Oポートには、内蔵しているI/Oユニットの機能が割り当てられています。8ピンDIP形状のATtiny85では、I/Oポート数が極端に少ないため、使い方が限定されます。
 例えば、以下の様な感じです。

 周辺に構築する回路に従い、何か使いたい機能がある場合、1)〜4)に例示したI/Oポートが、その機能によって占有されてしまうため、追加してUART通信に割り当て可能な端子は、その都度変化する事となります。
 「UARTを使う場合は、このI/Oポート」、と固定されてしまうと、そのI/Oポートに割り当てられた固有機能を使いたくなった場合にUARTとの同時使用が不可能となってしまうのです。

ATtiny85のメモリ容量

 ATtiny85のメモリ容量も、プログラム格納用Flashメモリが8Kバイト、データ格納用RAMが512バイトと、決して大きくありません。使用するライブラリなどのメモリ容量にも注意しなければならないのです。

 Arduino IDEに導入したATTinyCore環境には、ソフトウェアシリアルライブラリが備わっています。

  #include "TinySoftwareSerial.h"

 当方では未確認なので詳細は不明ですが、Streamクラスなどが継承されたクラスライブラリとなっていて、そこそこ物量が大きいと考えられます。
 つまり、使用する事はできるけど、メモリ容量が圧迫されると予想されます。

小さなソフトウェアシリアル

 つまるところ、ATtiny85でUART通信を使おうとすると、I/Oポートとメモリ容量の両面で厳しいことになる訳です。解決策は、自前で小さなソフトウェアシリアルを作る事、それが当方の結論です。
 以下に目標仕様をまとめてみます。

  小さなソフトウェアシリアル目標仕様:

 以降、目標仕様に基づいて詳細設計を進めて行きます。

実時間の計測

 ソフトウェアシリアルを作成する場合、通信速度に従ってデータを送受信するため、実時間の計測が必須です。
 また、通信速度に応じて、決まった時間にデータ通信を実行しなくてはならないので、送信または受信中に割り込みが発生してしまうと、通信を失敗します。少なくとも、1フレーム(10ビット、8N1)の送受信中は、割り込みを禁止しなくては、ならないのです。

※このため、送信処理と受信処理は同時に実行できず、双方向通信は半二重通信となります。

 割り込みを禁止した状態だと、millis()関数やmicros()関数等の実時間計測関数の実行に制限が生じます。これらの関数がタイマ割り込みを使って時間を計測しているためです。つまり、ソフトウェアシリアルの送受信関数内では、これらの実時間計測関数を使うことができません。

 millis()関数が使うタイマは8ビットタイマ(TC0)なので、今回は、TC0のカウンタレジスタTCNT0を直接アクセスして、実時間を計測します。
 例えば、送信関数は以下の通りとなります。

  // transmit frame data
  sreg_bak=SREG;
  noInterrupts ();
  cnt_next=TCNT0;
  for (idx=0; idx<n_frm; idx++)
  {
    cnt_next += tsws_cnt_1b;
    digitalWrite (TINY_SWSERIAL_TXD, (frm & 0x01)? HIGH: LOW);
    frm >>= 1;
    // wait 1 bit period
    while (TCNT0!=cnt_next)
      ;
  }
  SREG=sreg_bak;


 TC0は、millis()関数等の実装にも使われていますが、analogWrite()関数のPWM出力にも使用されています。このためコアライブラリでは、UNOのanalogWrite()関数と互換動作となる様、TC0のオーバフロウ周期は2,048μsに設定されています。
 カウンタTCNT0は、0 → 255までカウントして0に戻るので、1カウント当たりの時間は、2,048μs÷256カウント=8μsだと計算できます。このカウンタTCNT0を使って実時間を計測しながら、設定された通信速度(9,600baud等)で送受信するプログラムを作成しました。

※その後、ATtiny85のクロックを「Clock:"16 MHz (PLL)"」に設定すると、TC0の周期が半分の1,024μsになることが判りました。F_CPU=16MHzの場合には、1カウント当たり4μsで実時間を計測する様に対応してありますので、16MHzでも正しい速度で通信できます。

 なお、カウンタTCNT0で最大計測可能な時間は最長2,048μsなので、通信速度の下限値は1÷2,048μs≒489baud辺り+マージンです。UARTの通信速度を遅くして使いたいケースは、あんまり無いでしょうから問題無いと思われますが、頭の隅っこに入れておきましょう。
 通信速度の上限値はCPUの処理能力次第ですが、特に受信側が厳しいはずで、9,600baud(@F_CPU=8MHz)辺りが上限だと思われます。

受信に使う割り込み要因

 受信データは、いつ届くか判らないため、ATtiny85の端子変化割り込み(PCINT0)を使って待ち受けます。

  ISR (PCINT0_vect)
  {
    // PCINT0 interrupt service routine
    :
    : (省略)
    :
  }


 従って、受信機能を使う場合、ユーザがPCINT0割り込みを使用できなくなるのは仕様制限です。

 なお、端子変化割り込みはATtiny85の全汎用I/Oポートに割り当たっていて、PCINT0端子〜PCINT5端子まで6本が使用可能です(ピン配置はこちら)。どのI/Oポートの端子変化を割り込み対象とするか、PCMSKレジスタの対応するビット0〜5に"1"を設定することで選択可能です。
 そして、全ての端子変化割り込みは、唯一PCINT0割り込みとして発生します。このため、複数端子の端子変化割り込みを有効化すると、割り込みサービスルーチン(ISR)では、どの端子で割り込みが発生したのかを知る術がないので注意してください。
 今回の受信端子(RXD)は、PCINT0〜PCINT5の何れか1本だけなので、問題ありません。

プログラム

 以上の通り設計して、作成したライブラリを使い、Arduino IDEのシリアルモニタと通信するアプリケーションとして、Lチカプログラムを作成してみました。以下にプログラムとライブラリをリンクしておきます。

  プログラム:tiny_blink.ino
  ヘッダライブラリ:tiny_swserial.h

 2ファイルをダウンロードしたら、同じディレクトリに配置して、ATtiny85へコンパイル&書き込みを実行してください。

 ATtiny85へ書き込むと、シリアルモニタから以下のコマンドを受け付けています。

  シリアルモニタコマンド仕様:

 あとは、ATtiny85のD1端子(LED_BUILTIN)に接続したLEDをチカチカさせる、ただのLチカプログラムです。


 今回作成した、肝心なヘッダライブラリは、tiny_swserial.hです。
 皆様のプログラムで使用する場合には、お手元のプログラム(*.ino)と同じディレクトリにコピーして、#includeすると使える様になります。

  使用例)
    #define TINY_SWSERIAL_TXD 3
    #define TINY_SWSERIAL_RXD 4
    #define TINY_SWSERIAL_MAX 32
    #include "tiny_swserial.h"


 コンパイルオプションの指定が必要なので、#includeする前に、必要な#defineマクロを設定してください(上例の通り)。

小ぢんまりと実験

 それでは実験で動作を確認しておきましょう。以下表の通り、Arduino UNO、ATtiny85、USB⇔UART変換モジュール(FTDI等)、LEDを接続してください。

Arduino UNO 接続 ATtiny85 接続 USB⇔UART変換
モジュール(FTDI等)
  2 D3(TXD) RX
3 D4(RXD) TX
GND 4 GND GND
5V 8 VCC  
D13(SCK) 7 D2(SCK) 接続 LED_BUILTIN
D12(MISO) 6 D1(MISO) 抵抗(5〜10[kΩ])
  →LED→GND
D11(MOSI) 5 D0(MOSI)  
D10(SS) 1 D5(RESET)

 UNOをISP(書き込み装置)として使い、ATtiny85へプログラムコンパイル&書き込みしてください。
 シリアルモニタまたは、Tera Term等を使って、USB⇔UART変換モジュール(5V定格であること。 FTDIを使う場合は電圧ジャンパを5V側に設定)から、ATtiny85のソフトウェアシリアルにアクセスします。

 以下、実験中の様子です。

a297_expr.jpg

 上写真の通り、UNOとATtiny85はプログラム書き込み(シリアルプログラミングモード)用のSPI接続を確立したままで実験しました。シリアルプログラミング(SPI通信)に必要なI/Oポートを除く、空き端子にUART端子(TXD,RXD)を割り当てているため、同時にシリアルモニタを接続できました。
 D1端子に接続したLEDが、約1秒周期でチカチカ光っているはずです。
 これで、全8ピンを使い切っています🈵(1^^;)。

 こちらに例示した使用方法次第で、どのI/Oポートが空いているか変化するので、任意の空きI/Oポートにソフトウェアシリアルライブラリの通信線(TXD,RXD)を割り当てられることは、特筆すべき特長なのです。


 実験を進めましょう。

  実験サンプル:【 】内はATtiny85シリアルモニタからの送信データ
  【who】
  OK+ tiny_swserial.h library ver.1.00
  OK+ F_CPU=8000000[Hz] 9600[baud]
  OK: tiny_blink ver.1.00

 上に示した実験サンプルの通り、ソフトウェアシリアルを通してコマンドを実行し、その応答を確認しました。なお、ATtiny85はF_CPU=8MHzで使用していて、通信速度9,600baudで問題なく送受信できました(Good)。

 書き込み(シリアルプログラミングモード)と、Lチカに使用する端子を避け、ソフトウェアシリアル端子を任意に設定でき、プログラムサイズも以下の通り、十分小さく仕上げることができました。

最大8192バイトのフラッシュメモリのうち、スケッチが1924バイト(23%)を使っています。
最大512バイトのRAMのうち、グローバル変数が115バイト(22%)を使っていて、ローカル変数で397バイト使うことができます。

 作成したtinyソフトウェアシリアルライブラリの動作と効果が確認できたので、実験終了です。


 やはり、プログラムやシステムのデバッグにシリアルモニタくらい使えないと作業が捗らないので、いざと言う時、柔軟に使えて、メモリ容量も圧迫しない小さなソフトウェアシリアルがあると安心です。


btn_arduino.png

btn_prev.png btn_next.png

home