ATtiny

2026年4月30日 (木)

ATtiny3224の12bit ADC

ATtiny3224の12bit ADCを評価するため、電源回路
からあれこれと・・・

電源回路 主電源より0.1V~0.2V低い電圧の電源が欲しい
電源回路 主電源より0.1V~0.2V低い電圧の電源が欲しい #2

で、結局、こんな評価回路になりました。
Lm74

ラズパイ・ピコのAD入力テストでもマルチプレクサを
使って、PWMを用いたDACを試していました。

Arduino IDEでRaspberry Pi Pico:PWMでD/A出力してA/D入力を試す
Arduino IDEでRaspberry Pi Pico:A/D入力が…あれっ?

ATtiny3224での結果、そのうちまとめますが
あんまし良くありません。

単調性は大丈夫なのですが、12bit値として
想定より6~8くらい小さな値が出てくるのです。
いわゆるオフセット誤差です。
  電源電圧で変化
  Vref値で変化

データシートを見ると
代表値は「1.5LSB」と良い値が示されているのですが、
最大最小値が「±28LSB」とエラい大きな値になって
います。
Arduino UNO R3のATmega328Pの10bit ADCが
調子良かっただけに、ちょいと不満です。

取り急ぎ、ATtiny3224のADC特性です。
TCA0で12bitのPWM波を発生させ、LPFを通して
DAC出力を得ます。
それをAD変換。
出力も入力も12bitなので、その差を求めて正確さ
を見つめます。

まず、0~4095をスキャンしたときのようす。
電源電圧Vddは5.0V。
  DA電圧得るオペアンプは電圧を少し
  拡大して出力がサチるのを防いでいます。
VrefをVddと外部基準電圧2.5Vにして計ってみました。
Cap012_20260501131901
想定値より低い測定電圧になっています。
Vrefを2.5Vにするとさらに下がりました。
AD入力端子にVref電圧をつっこんでも、
フルスケールの4095は得られません。
VrefがVddなら電源につないでもフルスケール
値が出ないのです。

また、ゼロボルト付近の挙動もやっかいです。
Cap013_20260501132201
入力電圧が上昇してもしばらくゼロが続き、
あるところから変換値が出始めます。

ATtiny3224のAD部には差動増幅回路が仕込ま
れていて最大16倍のゲインを設定できるように
なっています。
この内部回路のせいで、単純なAD変換が苦手な
のでしょう。


もう一つ。
LM7705でマイナス電源を作ってオペアンプを
駆動したときの効果を。

LMC6482(今はエラい高価に)の出力特性が
改善されているようすです。

LMC6482での0V付近出力
+------------ 電源0~+5.0V
+ +---- -0.2V~+5.2V
PWM (mV) (mV)
0 6.9 0.2
1 6.9 1.3
2 7.1 2.4
3 7.2 3.7
4 7.4 5.0
5 7.8 6.2
6 8.3 7.4
7 9.0 8.5
8 10.1 10.0
9 11.0 11.0

いちばん右側に数字が±電源での出力電圧。
あたりまえですが、0V付近のリニアリティがちゃんと
なっています。
単電源だと入力が0Vでも出力は10mVくらいまで
しか落ちませんが、マイナス電源が入るだけで
ちゃんと0Vから出るようになります。

・ATtiny3224 12bit ADCテストプログラム
  ダウンロード - ad_test1.txt
    ファイルタイプをinoではなくtxtにしています

プロンプトに対し、
 ?:コマンド一覧表示
 数値(0~4096):DA用PWM値出力
 Enter(CR):PWM値と12 bit ADC値を表示
 G:スキャン開始 scan_lo→scan_hi
 L:スキャン開始PWM値
 H:スキャン終了PWM値
 S:スキャンstep
 W:スキャン待ち時間(約1ms)

 

| | コメント (0)

2026年4月24日 (金)

電源回路 主電源より0.1V~0.2V低い電圧の電源が欲しい #2

電源回路 主電源より0.1V~0.2V低い電圧の電源が欲しい
これの目的は、電源電圧の最大が6Vくらいの
入出力レールツーレール・オペアンプ、その
電源電圧をちょいと広くして、確実に0V付近と
電源電圧付近の出力を出したいぞ、です。

ATtiny3224などの「tinyAVR2」マイコンでは
ADCが12bitになってます。
  Arduino UNO R3のATmega328Pなど
  多くのAVRマイコンのADCは10bit。
その直線性などを確かめたいなぁという
試みです。

こんな回路で実験予定。
  まだこれから
Lm72
自分で出すPWMパルスをLPFに通して
DA変換。
それをAD変換して変化を見てみたろと
考えてます。

電源部はこんな回路にしました。
Lm71
プラス側はLM317Lを2つ。
VR200で+VLと+VHの電圧差を決めます。

マイナス側はLM7705-0.23Vを発生。
 ・マイナス電圧をちょこっと作ってくれるIC
この石を初めて使ってみます。

Lm73

tinyAVR2、いろんな内蔵基準電圧が使えますが
それを外に取り出せないのが欠点かと。
  VREFAに外から基準電圧を加えられるけど

| | コメント (0)

2026年4月 4日 (土)

ATtiny3224で電源周波数測定を試す #2

ATtiny3224で電源周波数測定を試す
自分でやってみて初めて分かった電源周波数の変動。
思いのほか動いてる!

電源周波数が低下した際に確認したいサイト:ラジオペンチ

長期的な記録などはラジオペンチさんなど他の方々にお願いして
「ほんとにこんなに変動するの?」を(ちょっとだけ)
追いかけてみました。

まず・・・
私の回路で周波数の検出に使ったのは
単純にフォトカプラ。
F12_20260402163901
発光素子は60Hzでの点滅を繰り返します。
このタイミングの変動はどうなんだ?
という疑問です。
  100V60Hzの正弦波の正側で発光。
  波形が下降してゼロクロス点手前で消灯。
  負側ではずっと消灯してて、上昇時、
  ゼロクロス点を通過して再点灯。
この点灯・消灯のポイントが変動すると、周期の計測
に影響するんじゃないかという心配です。

点灯・消灯の検出ポイントはフォトカプラの特性任せ。
現実に影響があるかどうかは不明ですが電源系負荷
の変動で「正弦波の形」が変動することもあるかと
思うのです。
  ピーク部が痩せた電源波形ってあるでしょ
この形の変動が、計測してる周期を振らせてるのじゃ
っと考えたのです。
フォトカプラの点灯・消灯スレッショルドの微妙な変動ですな。
  TIのAMC23C10のデータシートを見たからよけいです。

そこで、コンパレータを使い、できるだけゼロクロス点に
近いところを検出してみたのです。
こんな回路。
Ee51

D3とD4で入力正弦波を「絞り」、ゼロクロス付近
の波形を得ます。
これで、電圧が高い部分の波形変化は無視できます。
これをD5電圧の1/2を基準にして比較します。
TIのAMC23C10は完全に0Vで比較できますが、単電源で
使うLM393ではちょいとプラス目でないとダメ。

その結果。
昨日の昼過ぎから今朝まで。
Cap009_20260404110401
やっぱり、同じような変動が出ました。

そこで、検出周波数の分布を先日のフォトカプラだけ
で得たデータと比べてみました。
Cap008_20260404110501

コンパレータを使ったほう(緑)が、ちょっとだけ
裾の部分がせまくなりました。
しかし、これが検出回路を変えたからなのか、
元からこういう周波数制御だったのかは
わかりません。
  同時に比較しなくちゃ

検出回路は捨てようかと置いていたガラケーの充電器。
殻割りして元のスイッチング電源基板を取り外し、
プラグ部分が使えるようにして、回路を組み込みました。
J11_20260404111201
J12_20260404111201

周波数検出だけなら簡単なフォトカプラで大丈夫そうです。
無理して作るような回路じゃないかと。

今回の周波数計測、ATtiny3224の内蔵ハードウェアが頑張って
ますんでソフト的な変動は無視できます。
  Arduino環境ならバックグランドで動いてる
  タイマ割り込みとか、micros()の変動とか。

| | コメント (2)

2026年4月 2日 (木)

ATtiny3224で電源周波数測定を試す

ラジオペンチさんの「電源周波数測定・監視」
うまいこと周波数変化を計っておられるので、気になって
おりました。
 ・ATtiny3224のタイマでは・・・言うことを聞かないゾ
に記しました、「16bitタイマ TCB0とTCB1」の連結機能
による32bitカウント、
 「これで周波数カウンタを作るんだ!」
と訴えています。
そこで試したのが、
 ・ATtiny3224で裸の32bit周波数カウンタ #3
10MHzのTCXOを主クロックにして、周期を計る方法で周波数を
計ってみようという実験です。
32.768kHzの時計用水晶だと1秒のゲート時間に何発パルスが
来たかを数えて周波数を得ます。
必然的に周波数分解能は1Hz。
  これは高い周波数を計るとき向け

周波数の高い基準クロックで、入力パルスの周期を計る
方法だと、低い周波数を精度良く測定できます。
  手にいれた10MHzのTCXOだと0.1μ秒の分解能。
  60Hzのパルスを60発、1秒間累積すると
  1000万カウント。
  17カウントの違いが60.0001Hzあるいは
  59.9999Hzとなります。

ATtiny3224の場合、2つのTCBを連結して32bitカウンタ
を構成し、基準クロックをカウントします。
そして、もう一つあるタイマ機能、16bitのTCAで
入力パルスを数えて累積させ、所定の数になったら
TCBカウンタをキャプチャして、周期データを得ます。

・実験回路回路図
F14_20260402163101
16文字×2行の液晶表示とシリアル出力だけ。
スイッチはゲートモードの切り替え。

・試しの手組み回路
Ff11

・TCAとTCBの接続
F11_20260402163201
チップ内蔵のタイマ機能、みんな使ってる

・1秒ゲートモードでのカウント方法
F15

・周期測定モードでのカウント方法
F16

ハードは触らずに、うまいこと1秒ゲートと周期測定を
切り替えできます。

・電源周波数の取り込み
F12_20260402163901
簡単にフォトカプラで。

・試してみると
Cap006_20260402164301
20時間ほどのデータ。
  1秒に1行なんで7万行ほど。
60Hzからのズレをグラフに。
赤線はgnuplotの「smooth bezier」機能で平均化
した変動。

こちら、60Hzなんですが、けっこう変動があるものですなぁ。
ラジオペンチさんが「追跡」したくなるのがわかります。

※追記
計測開始から18時間30分ばかり(朝7時ごろ)のところに
急激な周波数変化が2回見えています。
その部分を拡大してみました。
   生の1秒ごとの周波数データをgnuplotで
   グラフに。平均処理せず。
Cap007_20260403085001
1回目(赤)は60.0Hzから4分ほどで60.2Hz付近まで上昇。
さらにそこから4分で59.86Hzまで低下。
2回目(緑)は、上昇→下降を10分ほどかけてという変化。
こんなデータが得られました。
3月31日の朝ってなにかありましたかな?

※ゲートモードの切り替え

/*****  ゲートモード *****/
byte gate_mode; // ゲートモード
// 0:1秒ゲート 1Hz単位の周波数計測
// 1~9:1/1~1/10000 周期計測モード
const struct{
const short fdiv; // 分周値
const char *msg; // 表示文字
}gate_tbl[]={
{ 0, "1sec " }, // 0 1秒ゲート
{ 1, "1/1 " }, // 1 周期計測
{ 10, "1/10 " }, // 2 〃
{ 50, "1/50 " }, // 3 〃
{ 60, "1/60 " }, // 4 〃
{ 100, "1/100" }, // 5 〃
{ 120, "1/120" }, // 6 〃
{ 440, "1/440" }, // 7 〃
{ 1000, "1/1k " }, // 8 〃
{ 10000, "1/10k" }, // 9 〃
};

こんなテーブルで1秒ゲートと周期計測による周波数測定
を切り替えています。
「fdiv」値をTCA0にセットして、入力パルスを「1/N」
しています。
SW1を押して選択。
長押しでEEPROMに現在値を保存。
  次の起動で読み出し。

※スケッチとシリアル出力、液晶表示ライブラリ
   ・ダウンロード - tn3224_fcnt03.zip

※参
「ゼロクロス検出回路」を検索していたらこんなICが
出てきました。
テキサスインスツルメンツ,絶縁型ゼロクロス検出回路:AMC23C10

※続き
ATtiny3224で電源周波数測定を試す #2

| | コメント (0)

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年3月12日 (木)

1Hzパルス発生回路改造

2026年2月 1日:備忘録:1Hzパルス発生回路
ATtiny402を使った1Hzパルス発生回路
これをちょっと手直ししました。
 ・空いている3つのポートを入力にして
  ロータリーDIPスイッチを接続。
 ・「0~7」の3bitで8種類の発生周波数を設定。
 ・オシレータ出力を増幅していた
  74LVC1GU04を74HC1GU04に交換。
    LVCだと電流消費大だった!
てな、手直しを施しました。

8種類の固定周波数しか出せませんが、安定した
60Hz(地元の電源周波数)が欲しかったので、
1Hzだけよりはマシかと、手持ち部品を活用しま
した。
まず回路図。
1hz10

電源は5V。
12.8MHzの発振モジュールは秋月で購入。

小さなプラ箱に入れてます。
1hz12

中の様子。
1hz11_20260312144301

Arduino IDE環境での制御スケッチ。

/**************************/
/* 方形波発生回路 */
/**************************/
// "TINY402_1HZ02.ino"
// 2026-03-12
// 12.8MHz TCXOをクロックに
// DSW(0~7)の設定で方形波を出力
// 0 1Hz
// 1 10Hz
// 2 50Hz
// 3 60Hz (60.000375Hz)
// 4 100Hz
// 5 120Hz (120.00075Hz)
// 6 440Hz (440.01375Hz)
// 7 1kHz
/***** 分周データテーブル *****/
// 周波数発生モードなので12.8MHzを1/2した
// 6.4MHzを原発振周波数と考える
const struct{
const word cmp0; // TCA0 CMP0データ 1/(n-1)
const byte clksel; // プリスケーラ設定値 + ENABLE(b0)
// 1/1,2,4,8,16,64,256,1024
}frq_tbl[] = {
{ 6250 - 1, 0b00001111 }, // 0 1Hz 1/1024 6250Hz
{ 2500 - 1, 0b00001101 }, // 1 10Hz 1/256 25kHz
{ 64000 - 1, 0b00000011 }, // 2 50Hz 1/2 3.2MHz
{ 53333 - 1, 0b00000011 }, // 3 60Hz 1/2 3.2MHz
{ 64000 - 1, 0b00000001 }, // 4 100Hz 1/1 6.4MHz
{ 53333 - 1, 0b00000001 }, // 5 120Hz 1/1 6.4MHz
{ 14545 - 1, 0b00000001 }, // 6 440Hz 1/1 6.4MHz
{ 6400 - 1, 0b00000001 }, // 7 1kHz 1/1 6.4MHz
};
/***** DSW読み込み *****/
// 0~7を返す
// PORTA offで1 onで0
// PA2:DSW-1 PA6:DSW-2 PA7:DSW-4
byte inpdsw(void)
{
byte n;
byte d = 0; // リターンデータ
n = ~PORTA.IN; // ポートA反転して入力
if(n & 0b00000100) d |= 1; // DSW-1
if(n & 0b01000000) d |= 2; // DSW-2
if(n & 0b10000000) d |= 4; // DSW-4
return d;
}
// こんなコードに展開されていた
// 90 91 08 04 lds r25, 0x0408 ;PORTA.IN
// 90 95 com r25
// 92 fb bst r25, 2 ;2
// 88 27 eor r24, r24
// 80 f9 bld r24, 0 ;1
// 96 fd sbrc r25, 6 ;6
// 82 60 ori r24, 0x02 ;2
// 97 fd sbrc r25, 7 ;7
// 84 60 ori r24, 0x04 ;4
// 08 95 ret
// bld,bst命令が珍しい
/***** DSWデータ *****/
byte dsw_now; // 安定した現在値(0~7)
byte f_dswok; // 変化確定フラグ dsw_nowに安定値
/***** DSW変化チェック *****/
// 確定したらf_dswokを1に dsw_nowが確定値
// PITの1024Hz周期で実行
void chkdsw(void)
{
static byte exc; // 実行区分
static byte tm; // 1msタイマ
// PITの1024Hz周期でダウンカウント
static byte chk; // DSW入力安定チェックデータ
byte d;
d = inpdsw(); // DSWデータ入力
if(tm) tm--; // DSWチェックタイマダウンカウント
switch(exc){
case 0: // 変化チェック
if(dsw_now != d){ // DSW変化あり
chk = d; // チェックデータ
tm = 20; // 20ms
exc = 1;
}
break;
case 1:
if(chk != d){ // 変化続く?
chk = d; // チェックデータ再セット
tm = 20; // タイマも再セット 20ms
}
else{ // 安定
if(tm == 0){ // タイムアップ
dsw_now = d; // 新DSW値
f_dswok = 1; // 確定フラグをon
exc = 0; // 次の変化待ちに
}
}
break;
}
}
/***** 周波数設定 *****/
// d:DSW設定値 0~7
// 分周値は-1してある CMP0にセット
void setfrq(byte d)
{
TCA0.SINGLE.CTRLA = 0; // TCA0 タイマー停止
TCA0.SINGLE.CMP0 = frq_tbl[d].cmp0; // 分周値
TCA0.SINGLE.CNT = 0; // カウント 0から
TCA0.SINGLE.CTRLA = frq_tbl[d].clksel; // プリスケーラ+ENABLE
}

/***** SETUP *****/
void setup() {
cli(); // 割込禁止
// ポート設定
PORTA.DIR = 0b00000010;
// || |||+---- PA0 6pin UPDI
// || ||+----- PA1 4pin out WO1 波形出力
// || |+------ PA2 5pin in DSW-1
// || +------- PA3 7pin in 12.8MHzクロック入力
// |+---------- PA6 2pin in DSW-2
// +----------- PA7 3pin in DSW-4
PORTA.PIN2CTRL = 0b00001000; // PA2 5pin DSW-1
PORTA.PIN3CTRL = 0b00001000; // PA3 7pin CLKIN
PORTA.PIN6CTRL = 0b00001000; // PA6 2pin DSW-2
PORTA.PIN7CTRL = 0b00001000; // PA7 3pin DSW-4
// +----- pull up有り
// クロック設定 PA3/CLKINに12.8MHz
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0); // プリスケーラなし
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 3); // 外部からクロック
// タイマーTCA0, 分割モードで初期化されるので16bitモードに
TCA0.SPLIT.CTRLA = 0; // タイマー停止
TCA0.SPLIT.CTRLESET = 0x0F; // 強制リセット
PORTMUX.CTRLC = 0; // TCAポート多重切り替えなしに
// タイマ設定
TCA0.SINGLE.CMP1 = 0; // PA1 4pin WO1
TCA0.SINGLE.CTRLB = 0b00100001;
// ||||+++----- WGM 波形生成FRQ(周波数)
// |||+-------- ALUPD
// ||+--------- CMP0EN
// |+---------- CMP1EN PA1 4pin WO1 出力
// +----------- CMP2EN PA2 5pin
// DSW(0~7)で周波数を設定
dsw_now = inpdsw(); // DSW値(0~7)
setfrq(dsw_now); // 8選択で1Hz~1kHzセット
// RTC,PIT 有効に
while((RTC.STATUS & 0x0F) || // RTC busy ?
(RTC.PITSTATUS & 1)); // PIT busy ?
// PIT分周操作
RTC.PITCTRLA = 0b00100001;
// |||| +-- PITEN PIT有効
// ++++----- PERIOD 1/32 →1024Hz
sei(); // 割込有効
}

/***** LOOP *****/
// DSWの変化をチェック
void loop() {
if(RTC.PITINTFLAGS & 1){ // 1024Hz(約1ms)周期
RTC.PITINTFLAGS = 1;
chkdsw(); // DSW変化チェック
}
if(f_dswok){ // DSW変化あり
f_dswok = 0;
setfrq(dsw_now); // 1Hz~1kHz周波数セット
}
}
#if 0
最大4096バイトのフラッシュメモリのうち、
スケッチが534バイト(13%)を使っています。
最大256バイトのRAMのうち、グローバル変数が
5バイト(1%)を使っていて、ローカル変数で
251バイト使うことができます。
#endif
// end of "TINY402_1HZ02.ino"

inpdsw()関数の下にあるコメントに書きましたが、
DSWのビット操作で「bst」と「bld」命令が出て
おりました。
ステータスレジスタの「Tフラグ」の応用、珍しい
のじゃないでしょか。

・Arduino IDEの設定
Ide_set02

書き込みはMPLAB@SNAPで。

※チャタリング対策
ロータリーDIPスイッチ、一般的なスイッチのチャタリング
除去だけじゃなく、回転が安定したことを確かめてから
設定値を更新しなくちゃなりません。
こんな手順です。
 ・約1ms周期でポートからDSWの値を入力。
 ・確定データから変化したかをチェック。
 ・変化があればそれをメモ。
 ・20msタイマをセット。
 ・メモしたのと変化があればタイマを再セット
  してチェック時間を延長。
 ・変化が無ければ(20m間)安定したと判断して
  確定データを更新し、新しくなったことを
  フラグで知らせる。
普通はタイマ割り込み内で処理するのですが、
今回は、他に時間がかかる処理が走っていないの
でメインループで処理しました。
  メインの処理はこれだけなんで。

実際の波形を見てもらいましょう。
I11_20260313105301

上側のch1は方形波出力。
設定7の1kHzと設定6の440Hzが見えています。

下側の波形はスイッチのbit0。
 設定7ならLレベル。
 設定6ならHレベル。
設定の変化から20ms後に周波数が変わっています。

そこにチャタリングを加えます。
 ・「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ

I12_20260313105501
bit0がバタバタしてる間は周波数は変化しません。
20ms間落ち着いてから周波数を変えています。

| | コメント (0)

2026年3月 7日 (土)

14ピンのATtiny3224のEVOUTB出力

 14ピンのATtiny3224、ピン配置にはイベント
出力としてEVOUTA(PA2)EVOUTB(PB2)の2つが記
されていますが、あれこれしてもEVOUTBが出力して
くれないんです。
EVOUTAはちゃんと出ます。

 EVOUTBの代替ポートが「ピンの外」なので、ひょっ
としたら最初から除外されているのかもしれません。
3224a

※青数字「4」のところっす。
ATtiny3224で裸の32bit周波数カウンタ #2

もうちょっと追いかけます。

※20ピンのATtiny3226でもEVOUTBが出力しません。
スケッチの該当部分。

//  EVENTを使ってEVOUTにタイミングを出力
EVSYS.CHANNEL1 = 0x80; // ch1入力:TCA0_OVF
EVSYS.USEREVSYSEVOUTA = 1 + 1; // ch1出力:EVOUTA(PA2) 出力
EVSYS.CHANNEL2 = 0x80; // ch2入力:TCA0_OVF
EVSYS.USEREVSYSEVOUTB = 2 + 1; // ch2出力:EVOUTB(PB2) 出力
EVSYS.CHANNEL3 = 0x80; // ch3入力:TCA0_OVF
EVSYS.USEREVSYSEVOUTC = 3 + 1; // ch3出力:EVOUTC(PC2) 出力

TCA0のオバーフロータイミングを3つのEVOUTに出力。
EVOUTA(PA2) とEVOUTC(PC2)はパルスが出ます。
EVOUTB(PB2)出力はLのまま。
なんででしょうね。

ATtiny3224もATtiny3226もEVOUTBの代替ピンは
ありません。

loop()内でPB2をトグルさせたらパルスが出たので、
イベント出力EVOUTBに切り替わっていないようです。

| | コメント (2)

2026年2月22日 (日)

tinyAVRマイコンのタイマーAとtakeOverTCA0()

Arduino IDE環境でのtinyAVRマイコン、
AVR0系、1系、2系とさまざまなのがありますが、
そのタイマーAは、本来16bitのタイマーなのですが、
2つの8bitタイマーで使うように初期化されています。
  PWM操作(8bitのanlogWrite出力)が目的

16bitタイマーで使うときは標準動作、
8bitタイマーだと分割動作と呼ばれています。

標準と分割とでレジスタの機能が異なっている部分が
あるので、実態は同じ制御レジスタなのに
  「TCA0.SINGLE.xxxx」と「TCA0.SPLIT.xxxx」
と名前を変えて呼ばれます。

問題なのが、タイマーAを16bitで使いたいとき。
分割動作で初期設定されたのを標準に戻すわけです。
データシートを見ますと、こんな記述が見つかります。
  (クリックで拡大↓)
Pp11_20260222114501

これ、リセットするのにArduino IDE環境では
takeOverTCA0()」を使うことになっています。
「megaTinyCore」の例題スケッチを探すとsetup()の
中でこの関数を一回だけ呼び、その後16bitの標準
動作の設定を行っているのがわかります。

takeOverTCA0()が何をしているかというと、
こんなソースが"wireing_analog.c"の中に
入っています。

void takeOverTCA0() {
TCA0.SPLIT.CTRLA = 0; // Stop TCA0 (1)
__PeripheralControl &= ~TIMERA0;
// Mark timer as user controlled (2)
TCA0.SPLIT.CTRLESET =
TCA_SPLIT_CMD_RESET_gc | 0x03;
// Reset TCA0 (3)
}

(1)はタイマーAの停止処理
   これは標準でも分割も同じ。
(2)は__PeripheralControlはPWM出力可能か
 どうかを保持している変数。
 タイマーAを止めてたらPWM出力できないぞ
 を返すために使われてる(みたい)
(3)が分割モードでのリセット処理。

一連の流れでタイマーAがリセットされるという
仕掛けです。

私の場合、takeOverTCA0()を知ったのはつい
こないだのこと。
これを使わずにどうしてたかというと、まず参考になるのが
  ・ATtiny1614:タイマレジスタの初期設定を見る
これで、タイマーAを含むタイマー関連の初期状態が
想像できます。

ATtiny402の初期状態をあらためて見ると、

ATtiny402 : timer Disable
TCA0.CTRLA 09
TCA0.CTRLB 00
TCA0.CTRLD 01
TCA0.EVCTRL 00
TCA0.INTCTRL 02
TCA0.PER FEFE
TCA0.PERB FFFF
PORTMUX.CTRLC 01

そして、ATtiny3224で裸の32bit周波数カウンタ #2
では、このようにレジスタ名SPLITは使わず、
SINGLEのまま16bitモードに。

  TCA0.SINGLE.INTCTRL= 0;        // 割り込み禁止
TCA0.SINGLE.CTRLA = 0; // TCA0 停止
TCA0.SINGLE.CTRLD = 0; // TCA0 8bit分割停止 16bitに

16bitモードの設定

ATtiny402マイコン サンプル:RCサーボモータテスター
では、

//  タイマーA0, 分割モードで初期化されるので16bitモードに
TCA0.SPLIT.CTRLA = 0; // タイマー停止
TCA0.SPLIT.INTCTRL = 0; // 割り込みなしに
PORTMUX.CTRLC = 0; // TCAポート多重切り替えなしに
TCA0.SINGLE.CTRLD = 0; // TCA0を16bitモードに

16bitモードの設定

手順はいろいろありますが、
 ・まずタイマーを止めて
 ・リセット
   これがtakeOverTCA0()の手順。
あるいは、
 ・タイマーを止めて
 ・割り込みをなしに
 ・16bitモードに
これでも大丈夫。

で、takeOverTCA0()でちょっと問題か?っと
思ったのが「PORTMUX.CTRLC」の設定。
8bit PWMでの出力を4ch使えるように「ポート多重器」
を使って、WO0をPA7に割り当てています。
これで、
 WO0 PA7 (代替ピン)
 WO1 PA1
 WO2 PA2
 WO3 PA3 (WO0/WO3共用)
と4つのPWM出力が使えるのです。

しかし、16bit PWMモードではWO0、1、2の3つの
出力しか使えません。
その中のWO0が代替ピンのままになっています。
  こうなってると分かっているのであれば
  良いのですが、takeOverTCA0()では
  ほったらかし。
16bit PWMの処理で、PA3がWO0出力になる
つもりのスケッチが、代替ピンのPA7に出て
しまいます。

完全に初期状態にというと、
「 PORTMUX.CTRLC = 0; // TCAポート多重切り替えなしに」
の1行が必要です。

※追記:ATtiny402、レジスタを表示してから
takeOverTCA0()を実行して、もう一度レジスタ表示。

ATtiny402 
 TCA0.CTRLA 09
 TCA0.CTRLB 00
 TCA0.CTRLD 01  ←8bit 分割モード
 TCA0.EVCTRL 00
 TCA0.INTCTRL 02
 TCA0.PER FEFE
 TCA0.PERB FFFF
 PORTMUX.CTRLC 01 ←TCA00代替ポートが有効

takeOverTCA0実行

 TCA0.CTRLA 00  ←初期値に
 TCA0.CTRLB 00
 TCA0.CTRLD 00
 TCA0.EVCTRL 00
 TCA0.INTCTRL 00
 TCA0.PER FFFF
 TCA0.PERB FFFF
 PORTMUX.CTRLC 01 ←代替ポート指定は残る


※追記 analogWriteを実行するとどうなるか
レジスタ値出力後、
 analogWrite(1, 1);     // PA7 3pin
 analogWrite(2, 2);     // PA1 4pin
 
analogWrite(3, 3);     // PA2 5pin
 analogWrite(4, 254);  // PA3 7pin
とanalogWrite で4つのPWMパルスを出力。
その後のレジスタ値の変化です。
 

ATtiny402 : 20MHz
analogWrite 前    後
-------------------------------
TCA0.CTRLA 0B 0B
TCA0.CTRLB 00 17  ←
TCA0.CTRLD 01 01
TCA0.EVCTRL 00 00
TCA0.INTCTRL 02 02
TCA0.PER FEFE FEFE
TCA0.PERB FFFF FFFF
TCA0.CMP0 0000 FE01 ←
TCA0.CMP1 0000 0002 ←
TCA0.CMP2 0000 0003 ←
PORTMUX.CTRLC 01 01

8bit分割モードで処理されたようすが見えてます。
ATtiny402でのanalogWrite、数値範囲は「0~255」。
0で全L。255で全Hなんで、128にしてもデューティ50%ジャスト
にはなりません。
50%よりちょっと大きめのデューティになります。
127だとちょっと小さめになり、analogWriteではジャスト
50%のデューティは出てきません。

Dt11

| | コメント (0)

2026年2月20日 (金)

ATtiny3224で裸の32bit周波数カウンタ #3

ATtiny3224で裸の32bit周波数カウンタ
ATtiny3224で裸の32bit周波数カウンタ #2
ここでは32.768kHzの時計用水晶発振子
基準クロックにして1秒ゲートを作り、
周波数を計れるようにしました。

これとは違うアプローチで、時計用水晶をやめて
マイコンの主クロックを
 TCXO(Temperature Compensated crystal Oscillator)
にして試してみようと、部品を手配。
共立から ECS-TXO-3225MV-100-TR という
10MHzの1.7V~3.6Vで使える「3225」サイズ
のを買ってみました。

とりあえず発振実験なんですが・・・
  ・・・ピッチ変換基板をどうしよう。

手持ちのをあれこれ探して、これで行こうと
なったのが「SOT-23-6」の変換基板。
この2ピンと5ピンのパターンを削ってハンダ付け。

Tcxo

TCXOを2つ買って、出てきた周波数、
うちの周波数カウンタでの計測値は
 10.000005MHz +0.5PPM
 10.000017MHz +1.7PPM
となりました。
TCXOのスペックでは、
 Frequency Tolerance(@+25℃±2℃) ±1.5 PPM
となってますんで、使ってる周波数カウンタの
精度が「どや?!」っとなってしまいます。

これを使っての周波数カウンタ実験はもうちょい先。

※続き
ATtiny3224で電源周波数測定を試す
ATtiny3224で電源周波数測定を試す #2

| | コメント (1)

2026年2月 1日 (日)

備忘録:1Hzパルス発生回路

トランジスタ技術2025年10月号
特集 今マイコンはArduinoが最強説』からみの備忘録。

p.121 図8 時計用水晶発振子を使った1Hzパルス発生回路
これの亜種を紹介しておきます。

記事では「HC4060」を2段接続して32.768kHzから
1Hzを得ました。
1hz_osc

これと同じ機能を得ます。
まずは、HC40460と「1G80」の組み合わせ。
1h31

次の2つは12.8MHzの高精度発振器を使って1Hzを得ます。
HC4060と10進カウンタHC393の組み合わせ。
1h32
単純に1Hzを得るためですが、ICの数が多くなります。

最後がマイコン。
1h33
水晶発振モジュールから出てくるのは、アナログ波形
なんで、マイコンのクロックとして使うには信号レベルを
大きくしなければなりません。
「1GU04」で増幅しています。

// ATtiny402で1Hzを作る(Microchip Studio)
#include <avr/io.h>
int main(void) {
PORTA.DIR = 0b11000110; // ポートの入出力
// || |||+---- PA0 6pin in UPDI
// || ||+----- PA1 4pin out 1Hz出力 (CMP1)
// || |+------ PA2 5pin out 1Hz出力 (CMP2)
// || +------- PA3 7pin in CLKin 12.8MHz発振器入力
// |+---------- PA6 2pin out (-)
// +----------- PA7 3pin out (-)
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0); // プリスケーラなし
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 3); // 外部からクロック
TCA0.SINGLE.CMP0 = 6250 - 1; // CMP0出力 1Hz
TCA0.SINGLE.CMP1 = 0; // PA1 4pin
TCA0.SINGLE.CMP2 = 3125; // PA2 5pin (1/4位相差)
TCA0.SINGLE.CTRLB = 0b01100001;
// ||||+++----- WGM 波形生成FRQ(周波数)
// |||+-------- ALUPD
// ||+--------- CMP0EN
// |+---------- CMP1EN PA1 4pin 出力
// +----------- CMP2EN PA2 5pin 出力
TCA0.SINGLE.CTRLA = 0b00001111;
// |||+----- ENABLE 動作開始
// +++------ CLKSEL プリスケーラ1/1024
while(1){ } // PA1,PA2に1Hzを出力し続ける
}

 

1hz11

配線して組み立てる工数からはマイコンを使うのが
いちばん簡単。
でも「チップにプログラムを書き込む」という壁が
存在します。

| | コメント (0)

より以前の記事一覧