1クロックでも速くしたい 割込を「ISR_NAKED」で
・2024年9月27日:1クロックでも速くしたい DDS方式の2パルス発生器
この続き。 タイマー1のコンペアマッチA割り込みを
インライン・アセンブラで書いてみました。
/***************************/
/* DDSでパルス出力 */
/***************************/
// A相B相出力データ COM1A,COM1Bで出力
volatile uint8_t com1_ab; // 3bitでCW,CCW判断
// tbl_ab[]をアクセス
volatile uint8_t tbl_ab[]={
0b11110000, // 000 H H CW
0b10110000, // 001 L H
0b10100000, // 010 L L
0b11100000, // 011 H L
0b11100000, // 100 H L CCW
0b10100000, // 101 L L
0b10110000, // 110 L H
0b11110000, // 111 H H
}; // |||| ++---- CTC OCR1A モード
// ||++-------- COM1B PB2 (00:off,01トグル,10:L,11:H)
// ++---------- COM1A PB1
/***** タイマー1 : コンペアマッチA割り込み *****/
// DDS処理
// CW/CCW出力 A:PB1(OC1A) B:PB2(OC1B)
// COM1(A,B) 00:off,01トグル,10:L,11:H
// PB4_H; // (!!!)
// dds_cnt += dds_step; // DDSカウンタをカウントアップ
// if(pls_on){ // パルス出力中
// com1_ab = ([dds_cnt.cnt1+3] / 4) & 0x03; // 1/(65536*4096)
// com1_ab |= pls_dir; // bit2でCW,CCWを区分
// }
// TCCR1A = tbl_ab[com1_ab]; // COM1A,COM1B制御 offなら前のb値で
// PB4_L; // (!!!)
ISR(TIMER1_COMPA_vect, ISR_NAKED)
{
__asm__ volatile(
" sbi %0, 4 \n" // PB4 H
" push r0 \n"
" in r0, __SREG__ \n"
" push r28 \n"
" push r29 \n"
" push r30 \n"
" push r31 \n"
// dds_cnt += dds_step;
" lds r28, (dds_step) \n" // LSB
" lds r30, (dds_cnt) \n"
" add r30, r28 \n"
" sts (dds_cnt), r30 \n"
" lds r28, (dds_step+1) \n" // +1
" lds r30, (dds_cnt+1) \n"
" adc r30, r28 \n"
" sts (dds_cnt+1), r30 \n"
" lds r28, (dds_step+2) \n" // +2
" lds r30, (dds_cnt+2) \n"
" adc r30, r28 \n"
" sts (dds_cnt+2), r30 \n"
" lds r28, (dds_step+3) \n" // +3
" lds r30, (dds_cnt+3) \n"
" adc r30, r28 \n"
" sts (dds_cnt+3), r30 \n"
// if(pls_on){
" lds r28, (pls_on) \n"
" and r28, r28 \n"
" breq j1 \n"
// com1_ab = ([dds_cnt+3] / 4) & 0x03;
" lsr r30 \n" // 1/2
" lsr r30 \n" // 1/4
" andi r30, 0x03 \n" // & 0x03
// com1_ab |= pls_dir; // bit2でCW,CCWを区分
" lds r28, (pls_dir) \n"
" or r30, r28 \n"
" sts (com1_ab), r30 \n"
" j1: \n"
// TCCR1A = tbl_ab[com1_ab];
" lds r30, (com1_ab) \n" // 0~7
" ldi r31, 0 \n"
" ldi r28, lo8(tbl_ab) \n" // tbl_ab[]
" ldi r29, hi8(tbl_ab) \n"
" add r30, r28 \n"
" adc r31, r29 \n"
" ld r28, z \n"
" sts %1, r28 \n" // TCCR1A
// pop reg
" pop r31 \n"
" pop r30 \n"
" pop r29 \n"
" pop r28 \n"
" out __SREG__, r0 \n"
" pop r0 \n"
" cbi %0, 4 \n" // PB4 L
" reti \n"
:
: "n" (_SFR_IO_ADDR(PORTB)), // %0
"n" (_SFR_MEM_ADDR(TCCR1A)) // %1
:
);
}
「ISR_NAKED」で、使うレジスタを我が手に。
その結果、
・pushとpopするレジスタの数を吟味して減らせる
・タイミングを見るためのポート出力を先頭と最後に
持ってこれるので、正確に見える。
問題はインライン・アセンブラの書式。
AVRマイコン特有の問題がまだ解決できていません。
「Z」レジスタにA相B相出力テーブルの先頭を持ってきているところ、
「c」だと「マイナスの値を引いて足し算」する処理が行われています。
820: e0 91 62 01 lds r30, 0x0162
824: f0 e0 ldi r31, 0x00
826: ed 5f subi r30, 0xFD
828: fe 4f sbci r31, 0xFE
82a: 80 81 ld r24, Z テーブル値を読み出し
これ、AVRマイコンの命令体系で、即値加算の命令が無いのです。
即値加算:addiとかadci
そこで、即値減算命令 subi、sbci命令を使い、
マイナスの値を引いて足し算に
ということで対処するのです。
AVRマイコン・アセンブラの定石
ところが、インライン・アセンブラの書式で、テーブルの先頭アドレスを
マイナスにする方法が分からないのです。
(- tbl_ab)とか(0 - tbl_ab)とかあれこれ
試しましたが、エラーが出ちゃいます。
そこで仕方なく、レジスタ同士の加算命令を使って、アドレスを
得ています。
これで、2命令を損
" lds r30, (com1_ab) \n" // 0~7
" ldi r31, 0 \n"
" ldi r28, lo8(tbl_ab) \n" // tbl_ab[]
" ldi r29, hi8(tbl_ab) \n"
" add r30, r28 \n"
" adc r31, r29 \n"
" ld r28, z \n" テーブル値の読み出し
これ、なんとかできれば良いのですが。
push、popするレジスタを5つまで減らせました。
元のは13個。
これでダイブとスピードアップ。
「union」を使った32bit値と8bit値のまぜこぜ処理も
無くなったし、ある意味、すっきりと。
※実行時間 割り込み処理およそ5μ秒に。
前は8μ秒ほど
最近のコメント