メ モ (項目を立てる程でも無いことを)
TOPページへもどる
液晶表示プログラム 060117 文字列定数をSRAMに置かない / 050628
タクトSWの計数(除チャタリング) 050807
ソフトウエアPWM 050810
mega48の高速PWM 050815 / 050814
tiny2313の高速PWM 050815 8ビットpwm・4出力
ChaNさんのCOMポートSPI Bridge 051221 9.216MHz版 / 051211 giveio.sysが要らないライタ
周波数カウンタの比較 KTXO-18S(12.8MHz) DS3231 DS3234 110911 掲示板から再掲
液晶表示プログラム
16文字×2行の液晶表示器のプログラムをブラックボックスとして扱いたい、と書いたことがあります。kusunokiさんが書かれたドライバを一部変更して
使わせていただいていますが、最新のものをここに置きます。akibowさんからSRAMに置かない文字列を教えていただいたので追加します。
設定上の注意は、
@lcd.h に データポート名(これは0〜3bitを使います)、コントロールポート名(bit番号は自由です)を指定します。
ALCD書き込み確認の読み出しはしていません。ディレイで使用しますからR/W端子はGNDに接続します。コントロールは RS と E の2つです。
Bディレイ(lcd.c)は、原本では4MHzで5000ですが、8MHzで5000でも動きました。4000では無理です。
Cデータおよびコントロールポートの方向(DDR)は、lcd_init()でデータの4bitsとコントロールの2bitsは出力に設定しますから、メインプログラムでは
これらのbitの方向を指定しなくてもかまいません。
Dlcd.c とlcd.h (ファイル名をlcd.hに変更)をメインプログラムと同じフォルダに置きます。
lcd.cとlcd.hのダウンロード用の圧縮ファイルはここにあります。
Emakefileの 「SRC = $(TARGET).c 」のところを「SRC = $(TARGET).c lcd.c」と変更します(半角空白が入っています)。
Fプログラムの中に
#include <avr/io.h>
#include <avr/pgmspace.h> // #include "lcd.h" より前に書く
#include "lcd.h"
を書き加えます。 システムにないインクルードファイルは "" で囲みます。 コンパイラはカレントディレクトリでなく、データディレクトリから検索します。
Gプログラムの中に 初期設定の関数 lcd_init(); を書きます。
以上の手続きをとっておけば次の関数を使って液晶に自由に表示できます。
lcd_cls(); 表示クリア
lcd_gotopos(12,1); カーソル移動。12,1は12カラム2行目を示します。カラムは0から始まります。
lcd_putch(c); 1文字表示。引数はアスキーコードか文字変数。
lcd_putstr("LCD test"); 文字列の表示。引数は文字列または文字列変数のポインタ。
文字列変数 s[10] にある時は lcd_putstr(s);とかきます。
lcd_putstrp("Hellow World"); 文字列定数の表示。前行ではSRAMにも置かれますが、これはFLASH ROMだけです。
最後の関数はメッセージの表示に便利です。 これでなければ、10文字のメッセージを5つ書けば50バイトのSRAMを消費する事になります。 2313は128バイトしか
ありませんから後の計算に支障が出ますが、最後の方法を使えば楽勝です(akibowさんに感謝)。
私が使っているコネクタ設定(基板を上から見て)です。コントラスト調整はコネクタの先LCDに付けます。(D0〜D3を訂正しました060522)
2 Vcc | 4 En | 6 D5 | 8 D7 |
1 GND | 3 RS | 5 D4 | 7 D6 |
タクトSWの計数(除チャタリング) ↑
タクトスイッチの押された回数をカウントアップして、カウントに応じた処理をさせるプログラムです。チャタリングを防ぐためにおよそ10ms毎にプルアップされた
スイッチの0を読みます。前回の値が0のときは押した状態からまだ離していない(押下継続)としてカウントしません。
ここにあるテキストの方が見やすいです。
volatile uint8_t cnt=0,s0=1,s1=1; // グローバル変数を指定します。
/* タイマー0の割り込みルーチン */
SIGNAL(SIG_OVERFLOW0) { /* SIG_OVERFLOW0はデバイスにより違います。シンボルを探してください。*/
TCNT0=216; /* 4MHz/(1024*40) = 100Hzを作ります。タイマーのカウンタセット TCNT0はデバイスにより違います。 */
s0=s1; // swを調べる 新データを旧データ変数に入れて
s1=(PIND & 1); // 新データを読み込む bit0にswがあるから off=1 on=0 bit3だったら PIND & 0b00001000 とする。
if(s0>s1){ // 旧=1、新=0 のとき(押された)
cnt++; // swのカウンタをインクリメント
if(cnt==4){cnt=0;} // 3までにする 変更でそれ以上にもできる
}
}
/* メインルーチン内の設定 */
DDRD=0b11111110; /* PORTD スイッチポートは入力モード */
PORTD=0b00000001; /* PORTD スイッチポートは pull up */
TCCR0=5; /* タイマ0の分周比=1/1024 */
TIMSK=(1<<TOIE0); /* タイマ0 割り込み許可 */
TCNT0=216; /* タイマーのカウンタセット */
sei(); /* 割り込み総元許可 */
|
ソフトウエアPWM ↑
多くのLEDをPWM制御することを聞きます。AVRのデバイスには2,3のPWMしかありません。どうするのかなをときどき思っていたところですが、
4つのLEDを制御する方法を尋ねられて思案していました。(tiny26Lには2つ、tiny2313には4つ、mega48には6つのpwmがあります。)
ふと、4つのポートをプログラムでon,offすればできるのではないかと考えました。50Hz程度でLEDのon,offをすると仮定します。1サイクル20msです。
LEDのデューティを1/100刻みにすると、1回の点滅制御の時間は 20ms*(1/100) で200μsとなります。したがって、200μs毎に割込をかけ、この
時間内に点滅処理ができれば制御可能ではないかと考えました。これだけの時間があれば8MHzクロックで200*8=1600のMCUサイクルとなりますから
オーバーヘッドや2ステップ命令があっても、いくつかのポートのon,offが可能だと考えました。
試しに作った8個のLED点滅のプログラムをここに置きます。
一応なめらかに点滅しています。
先人には当たり前かもしれませんが、このようなプログラムにはまだ出会っていなかったのでここに書いてみました。
mega48の高速PWM ↑
変更のためプログラムを差し替えました。
mega48に限りませんが、高速PWM機能を使うとPWM周波数が高い信号を得られます。8MHzのクロックで1/8分周しても約3.9kHzのPWM信号が使えます。
mega48には8ビットのカウンタが0,2の2種類あります(1は16ビット)。両方を使えば4つの高速PWMを利用できます。データシートを頼りに使い方を調べました。
試行錯誤の中、紆余曲折がありましたが、最終的に次のプログラムとなりました。
(4つのLEDを順次点滅(次第に暗く、明るく)するプログラムです。クロックは内蔵RC発振8MHzで、LED4個をPWM出力ポートにソース点灯でつないでいます。)
/**************************************************************************************************************
pwm4.c ATmega48 高速PWMの実験 050815 050814 im
2つの8ビットタイマ/カウンタ0,2を使って高速のPWM信号を4ポート( OC0A,OC0B,OC2A,OC2B )に取り出す。
クロック8MHzで分周比1なら31.25kHzのPWM周波数が得られる。1サイクル約30μsである。 8分周でも、3.9kHzとなる。
この例では、1つのLEDを 暗−明−暗 と等比級数的に変化させ(視覚にあわせるため)、かつ、4つのLEDがずれて点滅
するように設定してある。
メインルーチンでセンサによるコントロールを行えばバリエーションが可能になる。
***************************************************************************************************************/
#include <avr/io.h>
void wait(void){ /* 変化を遅くするための時間稼ぎ */
uint16_t j,k;
for(j=1;j<60000;j++){k=j;}
}
int main (void) { /* メインルーチン */
uint8_t a1,a2,a3,a4;
uint8_t d[57]={0,1,2,3,3,4,4,5,6,7,9,11,13,15,18,22,27,32,38,46,55,66,
79,95,114,137, 164,197,236,197,164,137,114,95,79,
66,55,46,38,32,27,22,18,15,13,11,9,7,6,5,4,4,3,3,2,1,0}; /* 明るさ変化のため等比数列 */
DDRB=0xff; /* 全ポートを出力としたが、必要なのは PD6,PD5,PB3,PD3 である */
DDRC=0xff; /* 〃 */
DDRD=0xff; /* 〃 */
TCCR0B=0x01; /* 1=分周無し 2=8分周 3=64分周 4=256分周 5=1024分周 */
TCCR0A=0b10100011; /* 1010=ABとも非反転 00=予約 11=高速PWM(TCCR0BのWGM02=0と) */
TCCR2B=0x01; /* 1=分周無し 2=8分周 3=32分周 4=64分周 5=128分周 */
TCCR2A=0b10100011;
a1=1; /* LEDの点滅をずらすためのオフセット */
a2=5;
a3=10;
a4=15;
for(;;){ /* メインの無限ループ PWMに関係なく自由に使える */
wait();wait(); /* 時間稼ぎ */
a1++;if(a1==57){a1=0;} /* 明るさ番号をインクリメント */
a2++;if(a2==57){a2=0;}
a3++;if(a3==57){a3=0;}
a4++;if(a4==57){a4=0;}
OCR0A=d[a1]; /* 明るさ番号に応じたデューティ比較値を設定 */
OCR0B=d[a2];
OCR2A=d[a3];
OCR2B=d[a4];
}
}
/* if(PORTD&_BV(4)){PORTD&=~_BV(4);}else{PORTD|=_BV(4);} トグル出力・ポートチェック用 */
要するに、@ポート出力の設定 ATCCRnxの設定 BOCRnx値の設定 の3つでできるようです。あとから考えれば単純ですが、
しばらくは泥沼に浸かっていました。
高速のPWM、4出力に成功しました。16ビットカウンタも使えば6出力が可能になります。
tiny2313の高速PWM ↑
変更のためプログラムを差し替えました。
tiny2313の16ビットカウンタを8ビットで使い、4出力の高速PWM制御を考えました。
上と同様に4つのLEDを順次点滅(次第に暗く、明るく)するプログラムです。クロックは内蔵RC発振8MHzで、LED4個をPWM出力ポートにソース点灯で
つないでいます。今回は1/8分周にしています。約3.9kHzになります。
/**********************************************************************************
pwmh1.c tiny2313 高速PWM 0508015 im
tiny2313の16ビットカウンタを 8ビット高速PWMとして使用する。
クロックは内蔵RC8MHz。PWMクロックは1/8分周。したがってPWM周波数は3.9kHz。
TCCR1A: COM1A1 COM1A0 COM1B1 COM1B0 ------ ------ WGM11 WGM10 1010 00 01 1010=一致でlow(AB)
TCCR1B: ICNC1 ICES1 ----- WGM13 WGM12 CS12 CS11 CS10 000 00 010 010=1/8分周
TCCR1C: FOC1A FOC1B - - - - - - 0000 0000
(WGM13 WGM12 WGM11 WGM10 = 0001 ・・8ビットPWM動作)
TCCR0A: COM0A1 COM0A0 COM0B1 COM0B0 - - WGM01 WGM00 1010 00 11 1010=一致でlow(AB)
TCCR0B: FOC0A FOC0B - - WGM02 CS02 CS01 CS00 00 00 0 010 010=1/8分周
(WGM=011 高速PWM)
*********************************************************************************** */
#include <avr/io.h>
void wait(void){ /* 変化を遅くするための時間稼ぎ */
uint16_t j,k;
for(j=1;j<60000;j++){k=j;}
}
/* メインルーチン */
int main (void) {
uint8_t a1,a2,a3,a4;
uint8_t d[57]={0,1,2,3,3,4,4,5,6,7,9,11,13,15,18,22,27,32,38,46,55,66,
79,95,114,137, 164,197,236,197,164,137,114,95,79,
66,55,46,38,32,27,22,18,15,13,11,9,7,6,5,4,4,3,3,2,1,0}; /* 明るさ変化のため等比数列 */
DDRB=0xff; /* 必要なのは PB2 PB3 PB4 */
DDRD=0xff; /* 必要なのは PD5 */
TCCR1A=0b10100001;
TCCR1B=0b00000010;
TCCR1C=0b00000000;
TCCR0A=0b10100011;
TCCR0B=0b00000010;
a1=1; /* LEDの点滅をずらすためのオフセット */
a2=5;
a3=10;
a4=15;
for(;;) { /* 無限ループ この中でデューティの値k1〜k4を自由に決められる */
wait();wait(); /* 時間稼ぎ */
a1++;if(a1==57){a1=0;} /* 明るさ番号をインクリメント */
a2++;if(a2==57){a2=0;}
a3++;if(a3==57){a3=0;}
a4++;if(a4==57){a4=0;}
OCR1A=d[a1]; /* 明るさ番号に応じたデューティ比較値を設定 */
OCR1B=d[a2];
OCR0A=d[a3];
OCR0B=d[a4];
}/* forの終わり */
}
/* メインルーチン終わり */
上と同じ内容で芸がないのですが、RGB+αのLEDを個別に明るさ調整しながら点灯できます。また、高速スイッチングですから、動体を照明しても
ちらつきが無いと思います。
プログラムの中でカウンタ1の処理を先に考えましたので、0と1の順序が逆になっています。
ChaNさんのCOMポートSPI Bridge ↑
AVRライタはCOMポート制御ISPアダプタを使ってきましたが、書き込み時にスイッチを入れる(押す)必要があります。その後発表された
COMポートから制御するシリアル−SPIブリッジは書き込み信号が自動化されていますのでスイッチを操作する必要がありません。USB-SPIブリッジを
テストしたときに快適だったのでシリアル版を作りました。
回路図
RS232-TTLの変換は略式で74HC04インバータを使っています。マイナスの電圧はどうなるのだろうと思いますが、GNDから入力端子へ向かうダイオードと
外付け100kΩで処理できているのでしょうか。以前のライタにも付けていたので、PCに接続すると赤いLEDが、またターゲットからVccをもらうと緑のLEDが点灯して
接続の確認ができるようにしています。モード切替スイッチは付けていますが、通常の書き込みには必要ありません。うまく働かないので悩みましたが、掲示板でお願いしましたら教えていただけました。
ポートの指定を -pc1 で無く、-pb1 にする必要があったのです。
マニュアルのavrx32.txtに明記されていました。
書き込みソフトはもちろん avrsp.exe です。
小さく作りました(基板サイズ32×53)ので、これから常用する予定です。
9.216MHz版−−−動いたことに気をよくして、akibowさんが 9.216MHz のセラミックレゾネータで作って居られたのを思い出しました。これで動くのなら、
入手しにくく価格の高い水晶(digi-keyで\79、高くないか でも運賃が)でなく秋月の9.22MHzセラロック\40が使えます。2つのコンデンサを抱かさなくてもいいのも
うれしいことです。上のものとほぼ同じ基板に組み立てました。
回路図は上のものと、水晶をセラロックに代えたこと、モードスイッチを省略したこと、LEDが抵抗付きの赤色に変わったこと以外はすべて同じです。ファームウエアは
akibowさんの ser2spi.hex(9.216MHz用) を頂戴いたしました。(感謝)
結果は機嫌良く動いてくれています。
この分ではChaNさんが公開してくれている 4MHz、6MHz、8MHz、10MHz も同様に作動するのではないでしょうか。これでokなら一般的なセラミックレゾネータでライタが
作製できることになります。(05.12.21)
(06.01.05追記)4MHzの水晶とセラロック、8MHzのセラロックを実験してみましたが誤差0のボーレイトを選んでも成功しませんでした。(残念)
(06.05.03追記)ChanさんのBBSレポートを思い出して、8MHzと4MHzに再度挑戦してみました。ファームウエア ser2spi.asm のクロックを変えて、ボーレートを
8MHzで 38400 にしてみました。このときの誤差は0.2%です。水晶がないのでセラロックで試したところ、デバイス情報の読みとりとプログラムの読み出しはできるよう
ですが書き込みではベリファイエラーになり、残念ながら使えませんでした。セラロックは手持ちの数個を試しましたが結果は同じでした。
4MHzのセラロックでは、ボーレート 19200 に設定しましたが、こちらは正常に使えるようです。ただ、115200 に比べると速度が遅くなりますので無理してまで使う
ことはないと思います。3.6864の水晶や9.22のセラロックが手にはいるまでの間に合わせには使えるようです。
なお、オプション -pb1:19200 を付けて実行しています。
周波数カウンタの比較 KTXO-18S(12.8MHz) DS3231 DS3234
今まで自作の周波数カウンタは基準時間を得るために秋月で扱われていたKTXO-18Sを使ったものでした。これ以外に安価で簡単に入手できる基準はありませんでした。しかし、MAXIMのRTCの精度が良いことを知ってこの1秒出力を基準時間にする周波数カウンタの製作実験をしました。
その結果の概要を掲示板から再掲します。
MAXIM RTC DS3234を基準時間とした周波数カウンタのバラックが完成しましたので報告します。
このRTCはDS3231のSPI版で秋月でも取り扱っています。
DS3231とほぼ同じ構成で、1HzをSQW端子から取り出してゲート時間を作りました。
16MHzの水晶発振器の4分周出力を測定したところ、
秋月の12.8MHz基準のカウンタで 3.999954〜55Hz
DS3231基準のカウンタで 3.999942〜43Hz
DS3234基準のカウンタで 3.999945〜46Hz
となりました。
秋月12.8MHzを基準にしますと、
DS3231 は -12Hz (3ppmクロックが速い)
DS3234 は - 9Hz (2ppm強クロックが速い)
となります。
真の値は不明ですが、これらの中間にあるとすれば±2ppm程度の正確さで使えるのではないでしょうか。
素人の基準を持たないケースではこの程度の誤差は問題になることはないようですから、いずれを基準に
しても実用になろうかと思います。
一般の水晶は50ppm程度と聞きますから一桁あまり正確であると言えるでしょうか。
なお、このDS3231は長時間の平均誤差が1.5ppm(クロックが速い)と報告した時計の物とは別の個体です。
DS3231はI2Cバス、DS3234はSPIバスの違いはありますがクロック生成回路は同じだと思います。当然ロットが違うはずですがこの2つの差は実験結果だけで見れば1ppm以下で、かなり正確ではないかと思います。スペックでは0〜40℃で±2ppmとなっています。
使用電源電圧は5Vですが、本来は3Vでクロックを調整してあるようで5Vではやや高めのクロックになるようです。別個体のDS3231使用時計は約4ヶ月の平均誤差が1.5ppm(クロックが高速)でしたので同様に考えれば少し補正が必要かもしれません。もし1ppm程度の補正が必要とすればこれらのRTCは素人に取っては極めて精度の高い基準として使えると思います。
↑