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バイトにした時の出力タイミングが
こんな様子です。
割り込みを使わないと、全部の文字が出力し終わるまで
処理から開放されません。
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"有効に。![]()
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オーバー
| 固定リンク
「Arduino」カテゴリの記事
- tinyAVRのシリアル出力(だけ)を割り込みでというライブラリ(2026.03.22)
- ATtiny1614:タイマレジスタの初期設定を見る(2026.01.06)
- ラジオペンチさんの「ダイソーのゆらゆらLEDキャンドルライト」#2(2025.09.15)
- ラジオペンチさんの「ダイソーのゆらゆらLEDキャンドルライト」(2025.09.11)
- ATtiny402サンプル:"Wire.h"を使わずI2Cで液晶表示 AQM1602だと(2025.09.09)
「ATtiny」カテゴリの記事
- ATtiny1614で屋根裏収納スペースの温度を記録する(2026.06.08)
- tinyAVR1の12bitタイマTCD0で2相パルス出力(2026.06.03)
- ATtiny3224の12bit ADC(2026.04.30)
- 電源回路 主電源より0.1V~0.2V低い電圧の電源が欲しい #2(2026.04.24)
- ATtiny3224で電源周波数測定を試す #2(2026.04.04)


コメント