2008/04/29(火)MegaAVR ADCの使い方
基本
データシート嫁。サンプルコードから盗み取るでもOK。
変換タイミング
ADCEN直後はアナログ回路の初期化で+12クロックを必要とします。サンプル&ホールド処理期間が+12クロックと見てよいでしょう。
通常時のS/Hは、1.5クロックを要します。電圧を保持するコンデンサは、14pFで、入力抵抗が1~100kΩある模様。キャパシタは(Vcc/2)と繋がっている模様なので、対GND電位ではない。
sample&hold time
14pF, ドライブ側の出力インピーダンスを10kΩとして, 110kΩで充電すると,
となります. 72%程度のチャージができる時間だったはず..なので、コレよりも数倍の時間を用意すべきです。200kHzが上限となっていたのですが, 時間にすると5uSecですね.上記の仮定から, おおよそ妥当な値と思います.
今回は, 7.3728MHzのCKを6分周して, 115.2Hzとするので, チャージ時間は十分でしょう.出力インピーダンスは... 場所によっては50kΩ程度になるのですが,まぁ大丈夫でしょう. (τ=2.1uSec)
2008/04/20(日)下まわり実装完了
前書き
ようやく、動作確認ができました。日々の生活リズムとか健康状態が重要ですネ。
もう少しだけ綺麗にしてからコードを公開します。ベースはあきぼうさんところから拾いましたが、誰が書いても同じようなコードになるのでアレですね。あと、インプリされていたコードのうち、Read系が壊滅的でした。自前のアセンブリコードを参照しつつ修正してなんとか動作にこぎつけました。データシートも見ずにやっていたのが最悪でした(^^;
あと、少しでも疑ったときには、そのコード全部書き直すつもりで取り組む必要があると認識しました。もう少しメジャーな公開されたライブラリを持ってくるほうが良かったですね。個人で情報を公開されていることについては、非常にありがたいので、これもまた個人ならでは、ということで。
基本機能
動作確認済み
基本、データビットは4ビットです。任意のビットから連続した4bitで動作可能です。アセンブリリストを見ると、少し冗長なのがきになりますが、アセンブラで書かないときついでしょう。
8ビットコードもありますが、動作確認できていません。リード系も実装しておきますかねぇ。
予定とか、仕様妄想
putchでもカーソル設定を毎回読んでいる実装なので、コレを修正する予定。コンソール出力イメージで使うケースと、ユーザで表示位置・文字数を全部調整したい場合とで切り替えたいでしょうし、マクロ化してしまうか、関数を分けるかしたほうがいいでしょうねぇ。個人的には、コンソール利用は殆ど無いと思うので、マクロで切って、標準はユーザでカーソル位置管理を任せたほうがよいでしょうね。
ケミコンの性能回復
nabeさんところのヘッドフォンアンプ記事に記載がありましたが、耐圧近傍で100h程度のエージングしないといけないのだとか。
今までそういうのは全くやってこなかったので、実は結構な損をしていたのかもしれませんねぇ。電源用途ばかりなので、A&V関係の製作は殆どありませんが。
そういえば、セレクタつきアンプってどうしたっけ....。どこかの箱に入ったままお蔵入りかも。メインアンプを取っ払ったから復帰させてもいいかな。
2008/04/17(木)[雑]LCDライブラリ自作(失敗
拙作ルーチンの移植~失敗(ぉ
あきぼう氏のavrgccでアセンブラを使うを参照し、アセンブラでライブラリを作成します。
;---------------------------------------- ;LCD_WriteCMD: LCDへコマンド1Byte送信 ;引数: register::LCD_DATA ;返値: なし ;---------- LCD_WriteCMD: rcall LCD_WaitBusy ; Wait for LCD BUSY SWAP LCD_DATA RCALL LCD_WriteCMDn ; $+1? LCD_WriteCMDn: mov tmp, LCD_DATA andi tmp, 0x0F ori tmp, (ID_DATA) ; P.U. out PORTD, tmp ; RS,R/W set sbi PORTD, LCD_E ; 2cycle, E Hi rjmp PC+1 SWAP LCD_DATA cbi PORTD, LCD_E ; 2cycle, E Lo ret
これ*1をエイヤこらどっこいしょとgcc向けにポーティングしようとして挫折。寝る前にさくっと終わるならいいかな?と思っていたけれども甘かった。いきなり汎用化記述を始めたのが敗因だなw。使用しているポートも変わるので、いっそのこと汎用化しようなんて考えたのが悪かった。
まぁ、寝不足に疲れのたまった脳では効率悪すぎたか(言い訳
氏のライブラリを伸ばして、コマンド待ちルーチン・データ入力ルーチンを追加することにしますかね。ウェイト部分をコマンドリードしてBUSY消えるのを待てばいいだけだしね。gccが吐き出したコードを見て、すげぇむだがあるようならアセンブラ化してしまいましょう。ただし、ユーザ定義のポート・ビットに耐えないといけないので、Cのままのほうが互換性は高そうですけれど・・・。無理にアセンブラ化しなくていいかなぁ・・。どうせLCDモジュールの処理待ちで待ち時間がかなりかかるはずだし。
ということはこの2時間ばかり捨てたということか('AP
2008/04/15(火)描画アルゴリズム
下調べの段階。昔も線分描画を作ったので流用可能ですが、再度調べてまとめておきたいと思います。といいつつ、ググって貼り付けるだけという酷い話で(ぉ
Fussyのホームページで詳しく解説されています。*1 X68erのようですねぇ。やはりこの辺の基礎がしっかりしているのには納得がいきますネ。パワーユーザばかりの印象があります。1人だけゲーマーとして持っている人を見た記憶がありますが('A`
とりあえず、補完のためコードだけ抜粋して流用したいと思います... dotを打つ処理が重いので、その点に関しては実装時に最適化をしてやる必要があります。とくにブレンド処理なんかを考えている場合は要注意です. VRAM外付け or 別デバイスのためにR/Wが遅いケースが致命傷です。マイコンのメモリに余裕があれば仮想VRAMとして割り当て、定期的に全画面転送したほうが無難かもしれません。
「Bresenhamの線分発生アルゴリズム」のサンプル・プログラム
void line( int x0, int y0, int x1, int y1, unsigned int col )
{
int i;
int dx = ( x1 > x0 ) ? x1 - x0 : x0 - x1;
int dy = ( y1 > y0 ) ? y1 - y0 : y0 - y1;
int sx = ( x1 > x0 ) ? 1 : -1;
int sy = ( y1 > y0 ) ? 1 : -1;
/* 傾きが1以下の場合 */
if( dx >= dy ) {
int E = -dx;
for( i = 0 ; i <= dx ; i++ ) {
pset( x0, y0, col );
x0 += sx;
E += 2 * dy;
if( E >= 0 ) {
y0 += sy;
E -= 2 * dx;
}
}
/* 傾きが1より大きい場合 */
} else {
int E = -dy;
for( i = 0 ; i <= dy ; i++ ) {
pset( x0, y0, col );
y0 += sy;
E += 2 * dx;
if( E >= 0 ) {
x0 += sx;
E -= 2 * dy;
}
}
}
}
クリッピング処理のサンプル・プログラム
/* 端点分類コード */
enum Edge {
LEFT = 1,
RIGHT = 2,
TOP = 4,
BOTTOM = 8,
};
/* 座標定義用構造体 */
typedef struct Coord
{
int x;
int y;
} Coord;
/* クリッピングエリア */
Coord Min, Max;
/*
calc_seq_code : 端点分類コードを求める
Coord c : 座標値
戻り値 : 端点分類コード
*/
int calc_seq_code( Coord c )
{
int code = 0;
if( c.x < Min.x ) code += LEFT;
if( c.x > Max.x ) code += RIGHT;
if( c.y < Min.y ) code += TOP;
if( c.y > Max.y ) code += BOTTOM;
return( code );
}
/*
calc_midP : 中点分割法による交点の算出メインルーチン
int a0, int b0, int a1, int b1 : 線分の座標
int clip_a : クリッピングする座標
クリッピング対象は bの方になる。
戻り値 : クリッピング結果
*/
int calc_midP( int a0, int b0, int a1, int b1, int clip_a )
{
/* もう一方の端点がウィンドウの辺上にある場合の対処 */
if ( a0 == clip_a ) return( b0 );
if ( a1 == clip_a ) return( b1 );
/*
線分の始点と終点を求める
(必ず sa < ea となるようにする)
*/
int sa, sb, ea, eb;
if ( a0 < a1 ) {
sa = a0, sb = b0;
ea = a1, eb = b1;
} else {
sa = a1, sb = b1;
ea = a0, eb = b0;
}
int ca, cb;
do {
ca = ( sa + ea ) >> 1;
cb = ( sb + eb ) >> 1;
if ( ca < clip_a ) {
sa = ca;
sb = cb;
} else {
ea = ca;
eb = cb;
}
} while ( ca != clip_a );
return( cb );
}
/*
calc_midP_x : 中点分割法による交点の算出(左右の辺)
Coord c0, c1 : クリッピング前の線分座標
int clip_x : 左(または右)の辺のX座標
Coord* c : クリッピング後の座標
戻り値 : クリッピング成功 >0 , 線分は完全に不可視 =0
*/
int calc_midP_x( Coord c0, Coord c1, int clip_x, Coord* c )
{
int cy = calc_midP( c0.x, c0.y, c1.x, c1.y, clip_x );
/* エリア外の場合はFalseを返す */
if ( ( cy < Min.y ) || ( cy > Max.y ) ) return( 0 );
c->x = clip_x;
c->y = cy;
return( 1 );
}
/*
calc_midP_y : 中点分割法による交点の算出(上下の辺)
Coord c0, c1 : クリッピング前の線分座標
int clip_y : 上(または下)の辺のY座標
Coord* c : クリッピング後の座標
戻り値 : クリッピング成功 >0 , 線分は完全に不可視 =0
*/
int calc_midP_y( Coord c0, Coord c1, int clip_y, Coord* c )
{
int cx = calc_midP( c0.y, c0.x, c1.y, c1.x, clip_y );
/* エリア外の場合はFalseを返す */
if ( ( cx < Min.x ) || ( cx > Max.x ) ) return( 0 );
c->x = cx;
c->y = clip_y;
return( 1 );
}
/*
calc_intsec_x : 数学的手法による交点の算出(左右の辺)
Coord c0, c1 : クリッピング前の線分座標
int clip_x : 左(または右)の辺のX座標
Coord* c : クリッピング後の座標
戻り値 : クリッピング成功 >0 , 線分は完全に不可視 =0
*/
int calc_intsec_x( Coord c0, Coord c1, int clip_x, Coord* c )
{
int cy = ( c1.y - c0.y ) * ( clip_x - c0.x ) / ( c1.x - c0.x ) + c0.y;
/* エリア外の場合はFalseを返す */
if ( ( cy < Min.y ) || ( cy > Max.y ) ) return( 0 );
c->x = clip_x;
c->y = cy;
return( 1 );
}
/*
calc_intsec_y : 数学的手法による交点の算出(上下の辺)
Coord c0, c1 : クリッピング前の線分座標
int clip_y : 上(または下)の辺のY座標
Coord* c : クリッピング後の座標
戻り値 : クリッピング成功 >0 , 線分は完全に不可視 =0
*/
int calc_intsec_y( Coord c0, Coord c1, int clip_y, Coord* c )
{
int cx = ( c1.x - c0.x ) * ( clip_y - c0.y ) / ( c1.y - c0.y ) + c0.x;
/* エリア外の場合はFalseを返す */
if ( ( cx < Min.x ) || ( cx > Max.x ) ) return( 0 );
c->x = cx;
c->y = clip_y;
return( 1 );
}
/*
クリッピング後の座標を求める
int code : 端点分類コード
Coord c0, c1 : 線分の座標
Coord* c : クリッピング後の座標
戻り値 : クリッピング成功 >0 , 線分は完全に不可視 =0
*/
int calc_clipped_point( int code, Coord c0, Coord c1, Coord* c )
{
/* ウィンドウの左端より外側にある */
if ( ( code & LEFT ) != 0 )
if ( calc_intsec_x( c0, c1, Min.x, c ) )
/* if ( calc_midP_x( c0, c1, Min.x, c ) ) */
return( 1 );
/* ウィンドウの右端より外側にある */
if ( ( code & RIGHT ) != 0 )
if ( calc_intsec_x( c0, c1, Max.x, c ) )
/* if ( calc_midP_x( c0, c1, Max.x, c ) ) */
return( 1 );
/* ウィンドウの上端より外側にある */
if ( ( code & TOP ) != 0)
if ( calc_intsec_y( c0, c1, Min.y, c ) )
/* if ( calc_midP_y( c0, c1, Min.y, c ) ) */
return( 1 );
/* ウィンドウの下端より外側にある */
if ( ( code & BOTTOM ) != 0 )
if ( calc_intsec_y( c0, c1, Max.y, c ) )
/* if ( calc_midP_y( c0, c1, Max.y, c ) ) */
return( 1 );
return( 0 ); /* クリッピングされなかった場合、線分は完全に不可視 */
}
/*
クリッピングメインルーチン
Coord* c0 : 始点の座標
Coord* c1 : 終点の座標
戻り値 :
>0 ... クリッピングされた
0 ... クリッピングの必要なし
<0 ... 線分は完全に不可視
*/
int clipping( Coord* c0, Coord* c1 )
{
int code0, code1; /* 端点分類コード */
code0 = calc_seq_code( *c0 ); /* 始点の端点分類コードを求める */
code1 = calc_seq_code( *c1 ); /* 終点の端点分類コードを求める */
/* 端点分類コードがどちらも0000ならばクリッピングは必要なし */
if ( ( code0 == 0 ) && ( code1 == 0 ) ) return( 0 );
/* 始点・終点の端点分類コードの論理積が非0ならば線分は完全に不可視 */
if ( ( code0 & code1 ) != 0 ) return( -1 );
/* 始点のクリッピング */
if( code0 != 0 )
if ( ! calc_clipped_point( code0, *c0, *c1, c0 ) )
return( -1 );
/* 終点のクリッピング */
if( code1 != 0 )
if ( ! calc_clipped_point( code1, *c0, *c1, c1 ) )
return( -1 );
return( 1 );
}
2008/04/13(日)キャラクタ液晶を使う
キャラクタLCDライブラリを使う
自作のLCDライブラリもありますが、フルアセンブラということもあり、WinAVRで即使用可能なものをググってみました。やっぱりありますよねぇ・・・。
あきぼう氏のコードを流用
あきぼうのAVRで遊ぶ日々を参照しましょう。AVRのLCD操作関数から、lcd.h, lcd.cとを持ってきます。
lcd.hで、使用する環境に合わせて変更するべき箇所があります。適当に抜粋します。
//#define LCD_RW_PORT PORTB // Port contains RS-pin //#define LCD_RW_DDR DDRB // Port contains RS-pin //#define LCD_RW 1 // portbitNo connected to LCD-RS #define N_LINE 4 #define N_COL 20 #define lcd_4 //#define lcd_nostring
これ以外に、LCD接続ポートの設定があります。上記を引用したのは、RW信号を未使用とした設定だったからです。あと、データ信号が4本使用時のため。コメントアウトされているところに、F_CPUをMakefileで定義するように記述されています。WinAVRを入れたディレクトリのavr/include/avr/util/delay.h を見るとわかりますが、空回りでウェイトを作っています。割り込み処理が多い場合は、規定時間をより多く取ってしまいそうですね。本当はfree run coutnerでスマートに作りたいところですが... まぁいいでしょう.
上記シンボルたちが何者か、と、lcd.cをさっと眺めて見ましたが、4bit data時には結構冗長かもしれませんねぇ。自作コードをgccにポートしたほうが良いかもしれませんね。余力があればもってきましょう。
ポーリングしてねぇ(confirm)
順番にやればいいのにざっとフレームを作ろうとして敗退。実機動作確認に至らず。時間待ちナシのAD変換できたら順次回すことにして、表示タイミングとチャタリング除去にタイマを使う方向で検討中。無論、AD変換精度向上のために、sleep命令は使用することにしますよ...。
で、先ほどwarning/errorなし状態にできたのですが・・・。lcd.cは、ウェイトで逃げてますね('A`
RW信号使ってないとは思っていましたが、コード自体も検証されていない様子。エラー出るしー。やっぱり自前の持ってきたほうがよさそうだわ。ぁ-どうすっかな。とりあえずコレでやり過ごして、その後持ってきたほうがいいかな。どんどんと完成が遠のいているのがよくわかるぜw
計算方法
固定少数とかなんだとか....。今回ADW(10bit)と、Vrefが2.5Vということで、綺麗に整数演算を済ますことができないと思われます。仕方がないので、可能な限り有効桁数を持って生きつつも演算量を減らすことを考えたいと思うます。
おおまかな考え方だけ転記しておきます。適宜、読み替えたりしてください。もちろんADWは複数読み出してはいけませんよ。また、右詰めを想定しています。
i = v / r v = 2.5 * (ADW / 1024) i = 2.5 / 1024 * ADW / 1 [A] i = 2.5 *1000 / 1024 * ADW [mA] => 0x9C4 * ADW >> 10 0x9C4 = ((0x27 << 6) | 0x04) i = ((0x27 * ADW) << 6 + (0x04 * ADW)) >> 10 (0x27 * ADW) >> 4 + (0x04 * ADW) >> 10 (0x27 * ADW) >> 4 + (ADW >> 8)
uint16_tだと16bit精度あるので, 10bit x 6bitまでならばoverflowしない、ということを用いています。uint16_t * uint16_tだと32bitの結果になるので、コードサイズが肥大化するのは目に見えていますから・・・。こういった制約がわかっている状況では、コードに素直に書く前に自力で展開してやることも必要ですネ。
ついでにr=10のときの数式も張っておきます。
R=10 i = v / r v = 2.5 * (ADW / 1024) i = 2.5 / 1024 * ADW / 10 [A] i = 2.5 *100 / 1024 * ADW [mA] => 0x0FA * ADW >> 10 0x0FA = ((0x03 << 6) | 0x2A) i = ((0x03 * ADW) << 6 + (0x2A * ADW)) >> 10 = ((0x03 * ADW) << 6 >> 10) + (0x2A * ADW) >> 10 = ((0x03 * ADW) >>4) + (0x2A * ADW) >> 10