« VCO IC「MAX2606」を使ったFMワイヤレスマイク | トップページ | ダイソー「LOOPER単4」(650mAh) 800サイクルで終了 »

2026年3月22日 (日)

tinyAVRのシリアル出力(だけ)を割り込みでというライブラリ

シリアル入出力といえば組み込みライブラリ「Serial」を
使った読み書きが一般的です。
Arduino UNO R3など、多くのツールではUSBラインを
通じてPCとつながっているので、IDEのシリアルモニタで
双方向のやりとりが可能です。
しかし・・・tinyAVRのようにチップ単体でモノを作るとなると、
 「受信はいらんねん。 送信だけあればエエんや。」
という場面が出てきます。
  送信だけ:マイコンで処理したデータの記録を
       PCなどでするという用途
そんなときに使うtinyAVR用のシリアル出力ルーチンを
まとめてみました。

この2つのファイルをスケッチファイル(".ino")と
同じフォルダにコピーします。
  "serial_tx_tn.h"   ヘッダファイル
  "serial_tx_tn.cpp"  ソースファイル

・ヘッダファイル "serial_tx_tn.h"

/********************************/
/* tinyAVR シリアル送信処理 */
/********************************/
// ヘッダーファイル
#ifndef _serial_tx_tn_h
#define _serial_tx_tn_h
#include <inttypes.h>
#include <Print.h>
// 定義
class serial_tx_tn : public Print{
public:
serial_tx_tn(uint8_t ch);
void begin(long);
virtual size_t write(uint8_t);
using Print::write;
};
#endif
// end of "serial_tx_tn.h"


・ソースファイル  "serial_tx_tn.cpp"

/********************************/
/* tinyAVR シリアル送信処理 */
/********************************/
#include "serial_tx_tn.h"
#include <inttypes.h>
#include <Arduino.h>
/***** 送信割り込みデータ ******/
volatile char tx_bff[48]; // 送信割り込み文字バッファ (A)
// チップのRAM容量に合わせて
volatile byte tx_rdp; // 送信 読み出しポインタ (B)
volatile byte tx_wrp; // 送信 書き込みポインタ (B) volatile byte tx_cnt; // 送信 データ数 (B) // バッファを大きくするならwordで
uint8_t tx_ch; // 送信チャンネル
/***** シリアル送信割り込み *****/
// 送信完了で起動
ISR(USART0_TXC_vect)
{
USART0.STATUS = USART_TXCIF_bm; // 送信完了解除
if(tx_cnt == 0){ // バッファに残データ無し
USART0.CTRLA &= ~USART_TXCIE_bm; // 送信完了割り込みオフに
}
else{ // バッファにデータあり
USART0.TXDATAL = tx_bff[tx_rdp]; // 1文字送信
tx_rdp++; // ポインタ+1
if(tx_rdp >= sizeof(tx_bff)){ // 最終?
tx_rdp = 0; // 先頭に
}
tx_cnt--; // データ数-1
}
}
/***** シリアル出力 *****/
// 1文字出力
size_t serial_tx_tn::write(uint8_t c)
{
volatile byte cnt; // サイズはtx_cntに合わせて (B) do{ // 送信バッファ空き待ち
cli(); // 割込禁止で
cnt = tx_cnt; // 送信バッファ内データ数
sei();
}while(cnt >= sizeof(tx_bff)); // 空きをチェック
// 初めてならいきなり出力
// 2回目以降は割込禁止にしてバッファに書き込む
cli(); // 割込禁止に
if(!(USART0.CTRLA & USART_TXCIE_bm)){ // 初めての送信
USART0.CTRLA |= USART_TXCIE_bm; // 送信割り込みon
USART0.TXDATAL = c; // 1文字直接送信
}
else{ // 送信継続中
tx_bff[tx_wrp] = c; // 1文字書き込み
tx_wrp++; // 書き込みポインタ+1
if(tx_wrp >= sizeof(tx_bff)){ // 最終?
tx_wrp = 0; // 先頭に
}
tx_cnt++; // データ数+1
}
sei(); // 割込有効に
return 1;
}
/***** コンストラクタ:送信chを記録 *****/
serial_tx_tn::serial_tx_tn(uint8_t ch)
{
tx_ch = ch; // (使っていない)
}
/***** 初期化 *****/
// USART0 シリアル出力だけ 受信はしない
// BPS:通信速度
void serial_tx_tn::begin(long bps){
USART0.CTRLA = 0; // 送信完了割り込みのセットはtx()で
USART0.CTRLB = 0b01000000;
// || ||||+-- MPCE
// || ||++--- RXMODE
// || |+----- ODME
// || +------ SFDEN
// |+-------- TXEN 送信有効
// +--------- RXEN 受信はしない
USART0.CTRLC = 0b00000011;
// |||||+++-- CHSIZE 8BIT
// ||||+----- SBMODE 1stop
// ||++------ PMODE Parity無し
// ++-------- CMODE 非同期USART
USART0.BAUD = (4L * F_CPU) / bps; // 10MHzなら4166
}
// end of "serial_tx_tn.cpp"

いくつか注意点があります。

・シリアル出力する「TXD0」ポートはユーザーが
 出力ポートに指定しなければなりません。
   代替ポートを使う場合もあるし、自動で
   出力になるようにはしていません。

・送信完了割り込みで次文字を出力しています。
 tx_bff[]のバイト数を大きくすると、文字列送信
 完了の待ちが短くなり、送信処理から早く解放
 されます。
 256バイト以上にしたい時は文字ポインタなどを
 wordにしなければなりません。(現在はbyte)

・文字、数値出力はSerialと同じように
   write  print  println  printf
 を使います。

サンプルスケッチです。

//  シリアル出力のサンプル
// マイコンのクロック周波数を出力
#include "serial_tx_tn.h" // シリアル出力制御ヘッダーファイル
serial_tx_tn SerialTX(0); // SerialTXの名でシリアル出力
/***** SETUP *****/
void setup() {
byte i;
float f;
PORTA.DIR = 0b11001110; // ポートを出力に (C)
// || |||+---- PA0 6pin in UPDI
// || ||+----- PA1 4pin out
// || |+------ PA2 5pin out
// || +------- PA3 7pin out (test)
// |+---------- PA6 2pin out TXD
// +----------- PA7 3pin out
SerialTX.begin(9600); // PA6 (2pin)がTXD出力
for(i=0;i<100;i++){ // 1sec待ち
delayMicroseconds(10000); // 10ms
}
VPORTA.OUT |= (1 << 3); // PA3(7pin) H (test)
SerialTX.println();
SerialTX.print("ATtiny402 : "); // タイトル
SerialTX.print(F_CPU); // クロック周波数
SerialTX.println("Hz");
SerialTX.printf("%lu.%03lukHz\r\n",
F_CPU / 1000L, F_CPU % 1000L);
f = (float)F_CPU; // クロック周波数をfloatに
SerialTX.printf("%fMHz\r\n", f / 1000000.0); // "%f"で
VPORTA.OUT &= ~(1 << 3); // PA3(7pin) L
}
/***** LOOP *****/
void loop(){ } // なにもしない


こんな出力が得られます。
  ATtiny402 : 16000000Hz
  16000.000kHz
  16.000000MHz

このルーチンを使うと、ROMサイズの関係でSerialでは無理だった
printf()で「"%f"」を使った浮動小数点出力ができました。
ギリギリですが、こんな使用量です。
  ROM 3889バイト(94%)
  RAM  65バイト(25%)

tx_bff[]を48バイトにした時の出力タイミングが
こんな様子です。
Aa11_20260322150901

割り込みを使わないと、全部の文字が出力し終わるまで
処理から開放されません。
tx_bff[]をもっと大きくすれば、気にならないくらい
の早さで戻ってきます。
  総文字数より大きな値にしたら、出力処理に
  食われた時間は1msほどでした。
    32bit値の数値変換
    floatの数値変換
ROMに余裕があれば標準のSerialを使えば良いでしょう。
しかし、ATtiny402のようにギリギリだと、お役に立てるかと。

・テストスケッチとライブラリ
  ・ダウンロード - serial_tx_tn.zip


※これも役に立つ(かも)
ATtiny402マイコン サンプル:I2Cで液晶表示(Wire.hを使わないで)
数値をBCD出力(表示)するルーチン #3

※「SerialTX.print」出力による「#if 0」の位置での
 プログラムサイズの変化
   ★1~★4はprintfでの"float"出力有効はなし。
   ★5で"float"有効に。

Pf11

  VPORTA.OUT  |=  (1 << 3);     // PA3(7pin) H
#if 0   ★1
SerialTX.println();
SerialTX.print("ATtiny402 : "); // タイトル
     ★2
SerialTX.print(F_CPU); // クロック周波数
SerialTX.println("Hz");
     ★3
SerialTX.printf("%lu.%03lukHz\r\n",
F_CPU / 1000L, F_CPU % 1000L);
     ★4
f = (float)F_CPU; // クロック周波数をfloatに
SerialTX.printf("%fMHz\r\n", f / 1000000.0); // "%f"で
     ★5
#endif
VPORTA.OUT &= ~(1 << 3); // PA3(7pin) L

★1 SerialTX.printなし     710バイト
★2 SerialTX.printで文字だけ  754バイト
★3 数値を出力         921バイト 
★4 printfで数値出力      2415バイト 
★5 float値出力         3889バイト

「SerialTX.print」を使っての数値出力だけなら
ほんとにROMを食いません。
printfでfloatを出力しなければROMが4096バイトの
ATtiny402でも安心して使えます。

「SerialTX」じゃなく「Serial」を使うと、
使わない受信処理までが組み込まれて
ROMエリアが膨れます。
「SerialTX」を「Serial」にしてROMサイズの変化を
試すとこんな具合。
★1 Serial.printなし     1368バイト
★2 Serial.printで文字だけ  1412バイト
★3 数値を出力        1585バイト 
★4 printfで数値出力    3079バイト 
★5 float値出力       4kオーバー





|

« VCO IC「MAX2606」を使ったFMワイヤレスマイク | トップページ | ダイソー「LOOPER単4」(650mAh) 800サイクルで終了 »

Arduino」カテゴリの記事

ATtiny」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




« VCO IC「MAX2606」を使ったFMワイヤレスマイク | トップページ | ダイソー「LOOPER単4」(650mAh) 800サイクルで終了 »