Arduino

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オーバー





| | コメント (0)

2026年1月 6日 (火)

ATtiny1614:タイマレジスタの初期設定を見る

AVR1系のATtiny1614(14pin)、Arduino IDEの設定で
タイマの使われ方が変わります。
Sc2

タイマレジスタの内容を表示するプログラムを書いて、
違いを見ました。
こんなスケッチ。

/***** ATtiny1614のタイマレジスタの初期値を見る ******/
// 配列のデータ数を返すマクロ
#define DIMSIZ(a) (sizeof(a)/sizeof(*a))
// チェックしたいレジスタ一覧
typedef struct{
const char *msg; // レジスタ表示名
byte type; // レジスタの読み出しバイト数 0:B,1:W
volatile void *p; // レジスタアドレス
}ST_REG;
ST_REG tmr_tbl[]={
{ "TCA0.CTRLA", 0, &TCA0.SINGLE.CTRLA },
{ "TCA0.CTRLB", 0, &TCA0.SINGLE.CTRLB },
{ "TCA0.CTRLD", 0, &TCA0.SINGLE.CTRLD },
{ "TCA0.INTCTRL", 0, &TCA0.SINGLE.INTCTRL },
{ "TCA0.PER", 1, &TCA0.SINGLE.PER },
{ "TCA0.PERB", 1, &TCA0.SINGLE.PERBUF },
{ "TCB0.CTRLA", 0, &TCB0.CTRLA },
{ "TCB0.CTRLB", 0, &TCB0.CTRLB },
{ "TCB0.INTCTRL", 0, &TCB0.INTCTRL },
{ "TCB0.CCMP", 1, &TCB0.CCMP },
{ "TCB1.CTRLA", 0, &TCB1.CTRLA },
{ "TCB1.CTRLB", 0, &TCB1.CTRLB },
{ "TCB1.INTCTRL", 0, &TCB1.INTCTRL },
{ "TCB1.CCMP", 1, &TCB1.CCMP },
{ "TCD0.CTRLA", 0, &TCD0.CTRLA },
{ "TCD0.CTRLB", 0, &TCD0.CTRLB },
{ "TCD0.CTRLC", 0, &TCD0.CTRLC },
{ "TCD0.CTRLD", 0, &TCD0.CTRLD },
{ "TCD0.INTCTRL", 0, &TCD0.INTCTRL },
};
/***** レジスタの状態出力 *****/
void dispreg(void)
{
byte i;
word d;
char const *ss[]={ // 出力区分
"%02X", // byte
"%04X", // word
};
for(i=0; i<DIMSIZ(tmr_tbl); i++){
switch(tmr_tbl[i].type){ // typeでbyte,wordを区分
case 0: // byte
d = (word)*(byte *)tmr_tbl[i].p;
break;
case 1: // word
d = *(word *)tmr_tbl[i].p;
break;
}
Serial.printf("%-16s", tmr_tbl[i].msg); // レジスタ名
Serial.printf(ss[tmr_tbl[i].type], d); // byte,wordデータ
Serial.println(); // 改行
}
}
/***** SETUP *****/
void setup() {
byte i;
Serial.begin(9600); // PB2(7pin)がTXD出力
for(i=0;i<100;i++){ // 1sec待ち
delayMicroseconds(10000); // 10ms
}
Serial.print("ATtiny1614 : "); // タイトル
Serial.println("timer TCB1");
dispreg(); // レジスタ値表示
}
/***** LOOP *****/
void loop() { // なにもしない
}


3つの状態を調べました。

ATtiny1614 : timer default
TCA0.CTRLA 0B clk_per/64
TCA0.CTRLB 00 出力はオフ
TCA0.CTRLD 01 8bit分割
TCA0.INTCTRL 00 割り込みなし
TCA0.PER FEFE 254,254
TCA0.PERB FFFF
TCB0.CTRLA 00
TCB0.CTRLB 00
TCB0.INTCTRL 00
TCB0.CCMP 0000
TCB1.CTRLA 00
TCB1.CTRLB 00
TCB1.INTCTRL 00
TCB1.CCMP 0000
TCD0.CTRLA 11 内部OSC,1/32,TCD有効
TCD0.CTRLB 00 1傾斜度動作
TCD0.CTRLC 80 波形選択
TCD0.CTRLD 00
TCD0.INTCTRL 01 オーバーフロー割り込み

ATtiny1614 : timer disable
TCA0.CTRLA 0B
TCA0.CTRLB 00
TCA0.CTRLD 01
TCA0.INTCTRL 00
TCA0.PER FEFE
TCA0.PERB FFFF
TCB0.CTRLA 00
TCB0.CTRLB 00
TCB0.INTCTRL 00
TCB0.CCMP 0000
TCB1.CTRLA 00
TCB1.CTRLB 00
TCB1.INTCTRL 00
TCB1.CCMP 0000
TCD0.CTRLA 00
TCD0.CTRLB 00
TCD0.CTRLC 00
TCD0.CTRLD 00
TCD0.INTCTRL 00

ATtiny1614 : timer TCB1
TCA0.CTRLA 0B
TCA0.CTRLB 00
TCA0.CTRLD 01
TCA0.INTCTRL 00
TCA0.PER FEFE
TCA0.PERB FFFF
TCB0.CTRLA 00
TCB0.CTRLB 00
TCB0.INTCTRL 00
TCB0.CCMP 0000
TCB1.CTRLA 03 clk_per/2
TCB1.CTRLB 00 周期割り込み
TCB1.INTCTRL 01 割込許可
TCB1.CCMP 270F =9999(10進)
TCD0.CTRLA 00
TCD0.CTRLB 00
TCD0.CTRLC 00
TCD0.CTRLD 00
TCD0.INTCTRL 00

・タイマの区分に関わらず、TCA0は8bit2分割モード
 でPWM出力用に初期化。
・「default」ならTCD0の割り込みで計時。
・「disable」ならTCB0,TCB1,TCD0は未使用。
・「TCB1」ならTCB0,TCD0は空き。

| | コメント (0)

2025年9月15日 (月)

ラジオペンチさんの「ダイソーのゆらゆらLEDキャンドルライト」#2

ラジオペンチさんの「ダイソーのゆらゆらLEDキャンドルライト」
3ch出力にした回路をでっち上げました。
Photo_20250915170901
図では出力コネクタが3つですが、2パラにして
(抵抗の左から分岐)合計6つのキャンドルを駆動
できるようにしました。
Cd21
  LEDの色を変えたのを追加
  VF=3Vほどの高輝度タイプは青と緑
  しか手持ちがなかった。
  元の橙色LED、発光角が広いのでしょう、
  手持ちの青と緑と違って「ろうそくの中」
  までエエ感じで光っています。
  青と緑の、中を照らすLEDを増設しようかしら。

LEDの直列抵抗、値を小さくして最大10mAほど
流れるようにしています。
  (ラジペンさんの回路だと560Ω)

Arduino UNO環境ですが、発振子無し
内蔵8MHzクロックで動かしています。
ATmega328Pに8MHz用ブートローダを書いて、
ヒューズを内蔵8MHzにしなくちゃなりません。

そして、Arduino IDEのボード選択を"UNO"ではなく
"Arduino Pro or Pro Mini"を選び、
プロセッサを
 "ATmega328p (3.3V,8MHz)"にします。

これで発振子無しの8MHzで動いてくれるはず。
  ・ダウンロード - candle01b.txt
    テキストファイル(UTF8N)にしてます
スケッチをアップできて、115.2kBPSで通信が
できてれば大丈夫じゃないかしら。
  PB5のLEDは0.2秒ちょい周期で点滅。

スケッチそのものは、8MHzも16MHzも変わりま
せんので、Arduino UNO R3環境でも手直しなしで
動作します。
  このあたり、たいへん便利なんですが、
  昔人間が思うところ・・・
   「どこでその違いを補正してるねん」
  が気になって、「16MHz→8MHzでも
  動くでぇ~」が「ほんまかいな」状態で
  落ち着きません。
  F_CPUが駆動周波数を示しています。
  それを「誰が(どのファイルが)、どこで(どのフォルダで)
  決めてるねん」をつかんでおきたいのですが、
  ワケワカメ。

元の基板のチップ部品3つを取り去り2つを間をジャンパ。
ダイオードもこの基板のコイル端子にハンダして3本の
電線を引き出しています。
Cd22

ゆらゆらキャンドル、「出窓」 へのデビューは女房次第でっす。
  『まだ「夏」のまんまやしなぁ~』
なんて言っとりましたんで。


そしてそして・・・
  ラジペンさんの「三つ編みケーブル」。
こちらでは手抜きで、ドリルを使って
電線をよじっています。
Cd24
チャックで電線を挟み込み、ギュイ~ン。
完成。
Cd25
  ※GX200で写したんだけどホワイトバランスに差がぁ。

| | コメント (4)

2025年9月11日 (木)

ラジオペンチさんの「ダイソーのゆらゆらLEDキャンドルライト」

ダイソーのゆらゆらLEDキャンドルライト(1/f ゆらぎに改造):ラジオペンチさん
「出窓」に面白そうと、ご近所のダイソーを3軒探したの
ですが、見つからず。
やむを得ず、ダイソーの通販に注文しました。
  送料がかかるけど・・・単3Looperもいっしょに
Cc11_20250911093901
  ※しかし・・・今朝見たら、商品ページが消えてました。
    ちゃんと届くのか不安がぁぁ

ラジオペンチさんのArduino UNOによる駆動系、
せっかくですんで使えるPWM出力6つを全部使って、
3つのキャンドルライトを駆動できるようにスケッチを
手直ししてみました。

・PWM出力チャンネル番号をテーブルに。
・明るさデータを3つの配列に。
・3chのループを回して独立して処理。
・「チラツキ防止のため、10回に分けて」
 「補間1ステップ時間(全体ではこの10倍)」
 は、loopの一番外側に「for(i = 1」を持ってきて
 コイル駆動は i = 1のときだけにして、LED処理
 は毎回で、というふうに。

// ラジオペンチさん:
// ダイソーのゆらゆらLEDキャンドルライト(1/f ゆらぎに改造)
// http://radiopench.blog96.fc2.com/blog-entry-1355.html
// 20250908_LedCandleCaosMethod.ino
// カオス方を使った 1/f ゆらぎを持つLEDキャンドル
// 2025/9/8 ラジオペンチ
// 9/11 3ch出力に改造:居酒屋ガレージ店主(JH3DBO)
byte COIL_PIN[] = { 6, 9, 11 }; // コイル駆動PWM出力 3ch
byte LED_PIN[] = { 5, 10, 3 }; // LED駆動
float xx[3] = { 0.7, 0.7, 0.7};
float last_xx[3];
float xxx[3];

void setup() {
Serial.begin(115200);
pinMode(13, OUTPUT); // LED
Serial.println("Start ");
}

void loop() {
PINB |= (1 << 5); // PB5 LED on/off
for (int i = 1; i <= 10; i++) { // チラツキ防止のため、10回に分けて、
for (byte j = 0; j < 3; j++){ // 3ch loop (0,1,2)
if (i == 1){ // 10loopの最初だけ実行
xx[j] = fluctuate(xx[j]); // カオス法で新しい明るさを決定
Serial.print(j);
Serial.print(", ");
Serial.print(xx[j]);
Serial.print(", ");
// コイルに通電して炎の板を揺らす
xxx[j] = last_xx[j] - xx[j]; // 明るさの差(減光量)が、
if (xxx[j] > 0.15) { // 一定値を超えていたら(値は要調整)
analogWrite(COIL_PIN[j], 255 * sqrt(xxx[j])); // コイルに通電
//(sqrtは値を1に近付けるため)
Serial.println(sqrt(xxx[j]));
} else { // 超えてなければ
analogWrite(COIL_PIN[j], 0); // コイル通電OFF
Serial.println(0);
}
}
// LEDの明るさを変える (i=1~10)
xxx[j] = last_xx[j] + (xx[j] - last_xx[j]) * i / 10.0; // 指定値まで直線補完で、
analogWrite(LED_PIN[j], 70 + xxx[j] * 180); // LEDの明るさを設定
last_xx[j] = xx[j];
}
delay(10); // 補間1ステップ時間(全体ではこの10倍)
}
}

float fluctuate(float x) { // 間欠カオス法で 1/f波形を生成
if (x < 0.5) { // 0.5以上なら
x = x + 2 * x * x; // 2x^2で徐々に増加
} else { // 0.5以下なら
x = x - 2 * (1.0 - x) * (1.0 - x); // 2(1-x)^2で徐々に減少
} //
if (x < 0.05 || x > 0.95) { // 結果が上下限を外れていたら、
x = random(100, 900) / 1000.0; // 乱数で0.1-0.9の範囲に戻す
}
return x;
}

こんな具合にスケッチはできたけど、キャンドルライトが
来ないことには試運転もできません。
  出力パルスをオシロで見てるだけ。

商品ページが消えてしまったキャンドルライト、
はたしてちゃんと届くのでしょうか。

※PWM出力の順番
 コイル駆動PWM出力 : 6, 9, 11
 LED駆動PWM出力 : 5, 10, 3 
これはATmega328Pにある3つのタイマーユニット
出力のペアに合わせてるので飛び飛びになってます。
  OC0A 6
  OC0B 5
  OC1A 9
  OC1B 10
  OC2A 11
  OC2B 3

※3ch版、完成形に
ラジオペンチさんの「ダイソーのゆらゆらLEDキャンドルライト」#2
  出窓へのデビューはいつになりますか・・・

| | コメント (1)

2025年9月 9日 (火)

ATtiny402サンプル:"Wire.h"を使わずI2Cで液晶表示 AQM1602だと

ATtiny402サンプル:I2Cで液晶表示 SCL周波数を設定できるようにしたら
で使った液晶AC0802は、コントラストを外付け
ボリュームで調整します。

AQM1602Y-NLW(FLW、FLB)やAQM1602XA-RNは
内部レジスタの操作でコントラストが変わります。

電源電圧3.3Vなら内部ブースター回路をオンに
して、液晶の駆動電圧を上げるので、5Vで使う
時とで微妙にコントラストが変わりますが
両方とも「35」あたりでOKのようです。

バックライト付のAQM1602Y-FLW-FBWを試したのが
このページ。 ※ずいぶん前だぁ
 ・秋月電子16文字x2行のI2Cインターフェース液晶AQM1602Y

今回はATtiny402でバックライト無しの
AQM1602Y-RN-GBWを試してみました。
  電源電圧は3.3Vでこんな回路。
Sch21
VRを回すとコントラストが変わります。
Cc01_20250909165301

※制御スケッチ
  ・ダウンロード - lcd_test2a.zip
    ※BCD変換ルーチンを別ファイルにしてあります。

これで、
  スケッチが1537バイト(37%)
  グローバル変数が37バイト(14%)
というメモリー使用量。

"Wire.h”をやめたおかげでRAMへの圧迫が
取り除かれています。

初期化のところで、文字数と行数、SCL周波数、
電源電圧を設定します。

  LCD.begin(16, 2, 100, LCD_V3R3);
   // I2C液晶 ★wire.hは使わない
   // 液晶初期化 16文字x2行,100kHz,3.3V
   // (5V:LCD_V5R0,3.3VならLCD_V3R3に)

3.3Vならブースター回路をオン。
5Vならオフに。

tiny402の動作周波数はArduino IDEの画面で設定
します。
3.3V動作なら10MHzが最大。
Ide02

電源電圧5Vでは必ず電圧ブースターをオフにという
のが注意点です。
  LCD.begin()での電圧設定をLCD_V5R0
  にしたらオフになります。

Arduinoでのアナログ入力といえば「analogRead() 」ですが、
tiny402には連続変換モード(自由走行という表現)というのが
あって、変換を始めたらいつでも最新値を読み出せるのです。
  入力チャンネルを切り替えるとなると
  タイミングがちょっとややこしいけど。
累積加算の機能と合わせて、割り込みで処理しなくて
良いのでなかなか便利。

もうひとつ。
内蔵クロック32.768kHzベースのRTCタイマに付随する
PIT(Periodic Interrupt Timer)が、メインクロック周波数が
変わっても一定時間を数えてくれるので、これも便利。
  通常はF_CPUで動作クロックが分かるのですが、
  例えばこのサンプルでは0.25秒(1/4秒)サイクル
  でPITフラグが立つようにしています。
  メイン周波数が変わっても、一定タイミングが
  得られます。
    delay()を使わなくてもエエんです。

| | コメント (0)

ATtiny402サンプル:I2Cで液晶表示 SCL周波数を設定できるようにしたら

ATtiny402マイコン サンプル:I2Cで液晶表示(Wire.hを使わないで)
これの続き。

I2C液晶、スペックを見ると電源5VならSCL周波数は
400kHzに対応しています。
そこで・・・液晶の文字数・行数をセットするときに
SCL周波数をセットできるようにしてみました。

//  I2C液晶
LCD.begin(8, 2, 400); // 液晶初期化 8文字x2行,SCL=400kHz

設定処理はこんな具合。
  WIFビットのタイムアウトチェックは無くしました。

/*****  TWI 初期化      *****/
// _scl_frq(kHz)からTWI周波数を設定
// MBAUD = F_CPU/2/fSCL - 5
void i2c_lcd_acm0802_tn::twiInit()
{
int16_t d;
// TWI0.MBAUD = 50; // TWI SCL周波数設定
// CPUクロックから計算してみる
d = F_CPU / 1000; // CPU クロック周波数(kHz)
d = (d / 2 / _scl_frq) - 5; // F_CPU/2/fSCL-5
if(d < 0) d = 1; // 1~255
if(d > 255) d = 255;
TWI0.MBAUD = d; // TWI SCL周波数設定
TWI0.MCTRLA = 0b00000001;
// || ||||+----- ENABLE 主装置許可
// || |||+------ SMEN 簡便動作しない
// || |++------- TIMEOUT 使わない
// || +--------- QCEN 迅速指令 しない
// |+----------- WIEN 書き込み割り込み なし
// +------------ RIEN 読み込み割り込み なし
TWI0.MSTATUS = 0b00000001;
// ||||||++----- BUSSTATE アイドル状態に
// |||||+------- BUSERR
// ||||+-------- ARBLOST
// |||+--------- RXACK
// ||+---------- CLKHOLD
// |+----------- WIF
// +------------ RIF
}
/***** TWI 書込み送出開始 *****/
void i2c_lcd_acm0802_tn::twiStartW(uint8_t adrs)
{
TWI0.MADDR = adrs << 1; // アドレス 書き込みで(0)
while(!(TWI0.MSTATUS & TWI_WIF_bm)); // WIF=1を待つ
}
/***** TWI データ書き込み *****/
void i2c_lcd_acm0802_tn::twiWrite(uint8_t data)
{
TWI0.MDATA = data; // データ書き込み
while(!(TWI0.MSTATUS & TWI_WIF_bm)); // WIF=1を待つ
}
/***** TWI 送出終了 *****/
void i2c_lcd_acm0802_tn::twiStop()
{
TWI0.MCTRLB = TWI_MCMD_STOP_gc; // Stop Condition
}

メイン側の記述でSCL周波数が設定できてエエかな
っと思ったのですが、「割り算」が入るので、単純に
「TWI0.MBAUD」に値を書くだけの元のに比べると
「90バイト」ほど肥大してしまいました。

ここらは目をつぶってSCL周波数を設定できるほうが
便利になるかと。
  100kHz→400kHzにしたら液晶表示のための
  処理速度が1データの書き込み単位で4倍に。

※スケッチ全体の様子
  ・ダウンロード - lcd_test1a.zip
    上で示したのとはちょっと変えてます

※表示画面
At41
回路図はこの記事の
 ・ATtiny402マイコン サンプル:I2Cで液晶表示

| | コメント (0)

2025年9月 8日 (月)

ATtiny402マイコン サンプル:I2Cで液晶表示(Wire.hを使わないで)

ATtiny402マイコン サンプル:I2Cで液晶表示 この続き。

ATtiny402での液晶表示、ちょっとでも「軽く」する
(メモリ使用量を減らす)ため、"Wire.h"を使わない
液晶表示ルーチンを仕立ててみました。

■LCD_TEST0 : "Wire.h"を使った場合

// "Wire.h"を使ってのACM0802液晶テスト
// ATtiny402 20MHz Arduino IDE環境
// PA0 6pin UPDI
// PA1 4pin - SDA I2C液晶
// PA2 5pin - SCL I2C液晶
// PA3 7pin out
// PA6 2pin out
// PA7 3pin out
// ボードマネージャー:
// http://drazzy.com/package_drazzy.com_index.json
#include "Wire.h" // I2C ACM0802液晶
#include "i2c_lcd_acm0802.h" // LCD制御ヘッダーファイル
i2c_lcd_acm0802 LCD(0x3C); // LCD I2Cアドレスを設定
/***** SETUP *****/
void setup() {
cli(); // 割込禁止
PORTA.DIR = 0b11001000; //ポート出力指定
// || |||+---- PA0 6pin UPDI
// || ||+----- PA1 4pin in SDA
// || |+------ PA2 5pin in SCL
// || +------- PA3 7pin out
// |+---------- PA6 2pin out
// +----------- PA7 3pin out
// PIT 1秒タイミング
RTC.CLKSEL = 0b00000001;
// ++---- INT1K 1024Hz
RTC.CTRLA = 0b00000001;
// ||||| +---- RTCEN RTC周辺機能許可
// |++++------- CLK_RTC DIV1 1/1
// +----------- RUNSTBY
RTC.PITCTRLA = 0b01001001;
// |||| +-- PITEN
// ++++----- PERIOD 1/1024 →1秒
// I2C液晶
Wire.begin(); // I2C開始
LCD.begin(8, 2); // 液晶初期化 8文字x2行
}
/***** LOOP ******/
void loop() {
byte c = '0';
LCD.setCursor(0, 0); // LCD上段
LCD.strdisp("Test LCD"); // タイトル
while(1){
if(RTC_PITINTFLAGS & 0x01){ // 1秒経過
RTC_PITINTFLAGS = 1; // PIフラグクリア
LCD.setCursor(7, 1); // LCD右下段
LCD.write(c); // 0~9
c++;
if(c > '9') c = '0';
}
}
}

結果。
  スケッチが1523バイト(37%)
  グローバル変数が100バイト(39%)

~~~~~~~~~~~~~~~~~~~~~~~~~~
■LCD_TEST1 : "Wire.h"を使わない場合

// "Wire.h"を使わないACM0802液晶テスト
#include "i2c_lcd_acm0802_tn.h" // LCD制御ヘッダーファイル
// ★wire.hは使わない
i2c_lcd_acm0802_tn LCD(0x3C); // LCD I2Cアドレスを設定
/***** SETUP *****/
void setup() {
cli(); // 割込禁止
PORTA.DIR = 0b11001000; //ポート出力指定
// || |||+---- PA0 6pin UPDI
// || ||+----- PA1 4pin in SDA
// || |+------ PA2 5pin in SCL
// || +------- PA3 7pin out
// |+---------- PA6 2pin out
// +----------- PA7 3pin out
// PIT 1秒タイミング
RTC.CLKSEL = 0b00000001;
// ++---- INT1K 1024Hz
RTC.CTRLA = 0b00000001;
// ||||| +---- RTCEN RTC周辺機能許可
// |++++------- CLK_RTC DIV1 1/1
// +----------- RUNSTBY
RTC.PITCTRLA = 0b01001001;
// |||| +-- PITEN
// ++++----- PERIOD 1/1024 →1秒
// I2C液晶
// Wire.begin(); // I2C開始 ★wire.hは使わない
LCD.begin(8, 2); // 液晶初期化 8文字x2行
}
/***** LOOP ******/
void loop() {
byte c = 'A';
LCD.setCursor(0, 0); // LCD上段
LCD.strdisp("Test LCD"); // タイトル
while(1){
if(RTC_PITINTFLAGS & 0x01){ // PIT 1秒経過
RTC_PITINTFLAGS = 1; // PIフラグクリア
LCD.setCursor(7, 1); // LCD右下段
LCD.write(c); // A~Z
c++;
if(c > 'Z') c = 'A';
}
}
}

結果。
  スケッチが773バイト(18%)
  グローバル変数が14バイト(5%)

8文字×2行の液晶、1行目にタイトルを表示して
右下に1文字の数字(TEST0)あるいは大文字英字
(TEST1)を1秒サイクルでインクリメント。

例題の違いはWire.hを使うか使わないか。
TEST0の下請けルーチン、i2c_lcd_acm0802は
これまでどおりにWire.hとtwi.hで制御。

TEST1のi2c_lcd_acm0802_tnはtiny402の
TWIレジスタを直接操作。
手抜きで、I2Cの制御にエラーが生じても(ACKが来ない
ゾ)そこでストップさせずにそのまま最後まで進んじゃ
います。

その結果、Wire.h仕様のTEST0だと、
  スケッチが1523バイト(37%)
  グローバル変数が100バイト(39%)

それがTEST1だと
  スケッチが773バイト(18%)
  グローバル変数が14バイト(5%)
と、大幅に使用メモリが少なくなりました。
RAMの使用量を減らせたのが大きいかと。

スケッチと下請けルーチンはダウンロードで。(zip圧縮)

 ・ダウンロード - lcd_test0.zip Wire.h使用

 ・ダウンロード - lcd_test1.zip TWIレジスタ直書きで


TEST1(i2c_lcd_acm0802_tn)で使ったTWIレジスタ
のアクセス方法。

/********************************/
/* TWIモジュールのアクセス */
/********************************/
// ATtiny402のTWIハードウェアを制御
// 割り込みを使わなない
// タイムアウトチェックカウンタを設けて異常でも抜けるように
// 正しくACKが帰ってこない場合も処理を続行
/***** TWI 初期化 *****/
// TWI周波数を設定 100kHz
void twiInit(void )
{
TWI0.MBAUD = 100; // 20MHz/2/fSCL
// 100kHz→100
// 400kHz→25
TWI0.MCTRLA = 0b00000001;
// || ||||+----- ENABLE 主装置許可
// || |||+------ SMEN 簡便動作しない
// || |++------- TIMEOUT 使わない
// || +--------- QCEN 迅速指令 しない
// |+----------- WIEN 書き込み割り込み なし
// +------------ RIEN 読み込み割り込み なし
TWI0.MSTATUS = 0b00000001;
// ||||||++----- BUSSTATE アイドル状態に
// |||||+------- BUSERR
// ||||+-------- ARBLOST
// |||+--------- RXACK
// ||+---------- CLKHOLD
// |+----------- WIF
// +------------ RIF
}
/***** TWI 書込み送出開始 *****/
void twiStartW(uint8_t adrs)
{
uint16_t tt; // time out check counter
TWI0.MADDR = adrs << 1; // アドレス 書き込みで(0)
tt = 2000; // 2000 loop
while(tt){ // time out?
if(TWI0.MSTATUS & TWI_WIF_bm) break; // WIF=1を待つ
tt--;
}
}
/***** TWI データ書き込み *****/
void twiWrite(uint8_t data)
{
uint16_t tt; // time out check counter
TWI0.MDATA = data; // データ書き込み
tt = 2000; // 2000 loop
while(tt){ // time out?
if(TWI0.MSTATUS & TWI_WIF_bm) break; // WIF=1を待つ
tt--;
}
}
/***** TWI 送出終了 *****/
void twiStop(void)
{
TWI0.MCTRLB = TWI_MCMD_STOP_gc; // Stop Condition
}

 

| | コメント (1)

2025年9月 5日 (金)

ATtiny402マイコン サンプル:I2Cで液晶表示

ATtiny402マイコン サンプル:割り込みでシリアル出力
ATtiny402マイコン サンプル:RCサーボモータテスター
に続いて、あらたなATtiny402マイコンのサンプルです。
これらに、I2C液晶を追加してみました。

Sch01

※参:Arduino IDEの環境
Ide01

・シリアル出力を割り込みで。
  出力完了を待たなくても、次の仕事が
  できる。

・A/D変換データ取得を割り込みで。
  変換完了を待たなくても最近値を
  いつでも読める。

・1ms割り込みでタイマー。
  この中でA/D変換開始を指令。


液晶表示の様子
Tn11

ざっと、タイミングを見てもらいましょう。
上から
  PWM出力 50Hz(20ms)でサーボ用パルスを出力
  シリアル出力データ(9600BPS)
  I2C SCL 液晶表示
  I2C SDA  〃

01m

4文字+6文字の液晶表示を行った後、シリアルデータを出力。
2msの時間待ちをしてから2文字を表示。
シリアル出力が割り込みで処理されるので、
2ms待ちと2文字表示が、シリアル出力完了を待たなくても
処理が進んでいます。

さて、なんとかしたいのが液晶表示のためのI2Cアクセス。
「Wire」「twi」ライブラリを使うので複雑になっています。
液晶表示だけなら書くだけ。
余計な処理はいらないので、もっと短くシンプルに
できるはず。

※参考
I2C液晶のアクセス、割り込み処理で遅れる原因らしきもの
I2C液晶のアクセス、割り込みで処理しないようにすると

Arduino UNO R3のI2C制御とATtiny402の制御が
異なるので、ATmega328用のプログラムは動きません。
もうちょい勉強です。

~~~~~~~~~~~~~~~~
  超低速2相パルス発生回路・ケース入れ で、
  I2C液晶表示をwireライブラリを使わない
  ようにしたら(wireの中からtwiを呼んでいる)、
  プログラムのサイズが1.8kバイトほど減少。
  RAMエリアも200バイトほど減りました。
~~~~~~~~~~~~~~~~
てな具合なので、なんとかしたいです。

※バックアップ代わりに今回のスケッチ
  液晶表示処理は別ファイルになってます
   ・ダウンロード - lcd_pwm_txint0.zip


※注意:I2Cのプルアップ抵抗
 ・秋月の液晶表示器 ACM0802C-NLW-BBW-IIC、I2Cのプルアップ抵抗
モジュールに2.2kΩのプルアップ抵抗が内蔵されています。

※続き
 ・ATtiny402マイコン サンプル:I2Cで液晶表示(Wire.hを使わないで)
   プログラムエリアとRAMエリアを縮小できます。

 ・ATtiny402サンプル:I2Cで液晶表示 SCL周波数を設定できるようにしたら


| | コメント (0)

2025年7月 3日 (木)

DDS IC「AD9833」をArduino UNO R3で制御:箱入れ #2

DDS IC「AD9833」をArduino UNO R3で制御:箱に入れる
ここからちょっと手直し。
 ・高い周波数で方形波を出力するとちゃんとした
  波形が出てこない。原因はアンプの飽和。
 ・AD9833の出力、正弦波と三角波だとおよそ
  0.6V(p-p)なのが、方形波だと電源電圧フルスイング
  になってしまう。
 ・このため、前の回路では前段のアンプ(4V(p-p)まで増幅)
  が飽和。
 ・そこで、方形波の時にレベルを下げるような細工
  を入れ込む。
 ・使ったのは2入力のアナログマルチプレクサ。
    74LVC2G53 8pinのIC
      「2G」なんで2ゲートかと思いきや
      機能ブロック的には一つだけ。
      なんかへんな感じ。
 ・方形波を出力する時は、出力レベルを1/9した側に
  切り替える。
 ・飽和しないしLPFを通らないので、まっとうな
  方形波(今度はアンプでの帯域制限)が出るように。

ということで、こんな回路になりました。
  ※回路変更あり(一連のコメントを参照)
Ad9833
・BSch3Vのデータ
   ・ダウンロード - ad9833_04.ce3

・切り替え機能を追加したスケッチ。
   ・ダウンロード - ad9833_03.zip

・HEXファイル
   ・ダウンロード - ad9833_03_hex.zip
 AD9833_03.ino.hex
 AD9833_03.ino.with_bootloader.hex
この2つ。

AD9833を使う時の参考にどうぞ。

周波数を高くするときにいるのは、やっぱ、フィルタです。
これ以上の高速アンプは電気食いだし、電池運用はちょい
としんどいか。

※回路図をちょっと訂正
LVC2G53の手前の2段CRフィルタ。
マルチプレクサの入力容量がけっこうあるようなので
(データシートでオン時約20PF)フィルタ部を変更しました。
9833a
  入力部の抵抗を200Ω→1Kに。
  コンデンサをなしに。
1MHzあたりからレベルが落ち始めます。
4MHzになると出力電圧はほぼ半分に。

※追記
AD9833の出力を
  ・出力レベルを可変したい
  ・出力ののDCオフセット電圧を可変したい
ときの方法、面倒でもこんな構成かと。
Pm4
方形波の時はLPFを通さずできるだけ「生」信号
を使えるよう、MPXで分けます。
正弦波、三角波の時だけLPFを通します。
12dB/octので描きましたが、2段にして24dB/octや
LCフィルタでも良いわけです。
方形波出力のとき、後段アンプを飽和させないように
します。
  飽和させると電源ラインが汚くなります。
周波数が高くなると、高抵抗が使えなくなるので
ボリュームの入力部が面倒になります。

現スケッチのままで動くように
 マルチプレクサのプルアップ抵抗をプルダウンに。

04a


| | コメント (31)

2025年6月30日 (月)

DDS IC「AD9833」をArduino UNO R3で制御:箱に入れる

DDS IC「AD9833」をArduino UNO R3で制御
DDS IC「AD9833」の出力にバッファアンプを
DDS IC「AD9833」の出力にバッファアンプを #2

この時は、むき出しのユニバーサル基板でした。
便利に使おうとすると、
 ・箱に入ってて欲しい
 ・電池運用したい
となってしまいます。

百均屋で買ってきた透明樹脂の「箱」に入れました。
  回路は、元のとほぼ同じ。
  出力アンプはOPA2863で。
  1MHzちょいが安定して出ればOK。
  ほんとは出力レベルを可変したかったけど、
  DCオフセットの調整だけ。

Sw11
手組みした基板の様子。
Ss12_20250630150301
回路図。 (クリックで拡大↓)
Ad9833_dds03
スケッチ
  ・ダウンロード - ad9833_02.zip

AD9833のクロックをATmega328Pから出してますんで
ヒューズを書き換えて、16MHzクロックを出すようにし
なくちゃなりません。
  テストするだけならPB1に出してる8MHzも
  使えます。

役に立つかどうかは不明ですが、周波数スイープ機能を
入れてます。
  ラジオのIFトランスの調整あたりに
  使えないかなぁ。
ジャンクボックス内で拾った455kHzのセラミック・フィルタ
とTrラジオのIFTで試してみました。
Sw13

Sw14
フィルタの中心、455kHzが見えています。

スイープ速度を速くすると、周波数計算を行っている
サイクルが見えてきます。
Sw15
計算はもっと早くできるんですが液晶表示が遅い。
液晶表示は0.1秒に1回。

もうひとつ。
AD9833をはじめ、さまざまなDDS IC、無線家の皆さんが
VFOとして使われることが多いようで「スプリアス」を
気にされています。
しかし・・・原理的に、クロック周波数に近い、高い周波数
を出そうとすると、DDS処理に絡むジッタの割合が大きく
なり、きれいな波形が得られません。

これ、正弦波じゃなく方形波(DACデータのMSB)で見ると、
「なんじゃこりゃ」が見えてきます。
今回の回路では16MHzが基準クロック。
割り切れる8MHzや4MHz、2MHz、1.6MHz、1MHzは
きれいな波が出てきます。

4MHzだとまっとうな方形波。
Aa001_20250630170201
16クロックで1μsになり、そこに4発の方形波
が並びます。
1発あたり4クロック。

ところが5MHzになると・・・
  発生周波数を自由にできるのが
  DDSの取り柄なんですけれど。
Aa000_20250630170401
1μs間に5発のパルスが続くんで間違いなく5MHz。
でもそのパルスの間隔、周期は1クロック幅で右に
行ったり左に行ったり。

例えば、1秒ゲートの周波数カウンタで計ったら
こんな波形でもほんとにジャスト5MHzになります。
  それがDDSの原理。

オシロで単発波形を採るんじゃなくちょいと
流してみるとこんな波形になります。
Aa002
「なんじゃこりゃ」のデジタルデータを使って正弦波の
データが入ったROMをアクセスしてD/A変換。
周波数が高いほど、影響が大きく出てきます。
この波形が元になるんで、
  「スプリアス、そりゃ出まっせ」
かと。
クロック周波数に近い、高い周波数が欲しいときは、
出す周波数ギリギリのフィルタで高調波を落と
すしかないでしょ。

※追記
もう一つ、スイープ波形。
制限波出力で1kHz→4MHzを4秒かけてスイープ。
 上段がスイープトリガ用出力。
 中段がAD9833の出力。
 下段だOPA2863を2段通った出力。
  リニアでのスイープなんでこういった周波数特性
  を見る時はLOGスイープが欲しくなります。

Wv12
4MHzになるとレベルが約半分に。
OPA2863の性能が見えてます。
  オペアンプ前段に入れた220Ω+100PF2段の
  LPFのせいです。
  OPA2863はもっと優秀。

※MAX038発振器では ・・・
昔に組んだMAX038発振器では出力オペアンプとして
「AD811」を使ってました。
  帯域幅    140MHz
  スルーレート 2500V/μs
DigiKeyでの現在価格2200円。

※続き
DDS IC「AD9833」をArduino UNO R3で制御:箱入れ #2


| | コメント (0)

より以前の記事一覧