円周率の計算 (13) -- BBP 公式による計算 (2)

今回は AVRStudio で ATtiny13A 用にコンパイルするソースファイル一式を掲載します。
使用したのは

  • AVRStudio 4.19 (build 730)
  • gcc Version 4.5.1 (AVR_8bit_GNU_Toolchain_3.3.0_364)

の組み合わせで、他の環境では試していません。
まず、AVRStudio で ATtiny13A 用の C プロジェクトを立ち上げ、最適化レベルは「-Os」(サイズ優先) に設定し、下記のファイルをメインの C ソースファイルとしてコンパイル対象となるようにプロジェクトに追加します。
「pi_BBP.c」

/*************************************************/
/* pi_BBP.c : Pi calculation by BBP algorithm    */
/*            up to 4096 hexadecimal digits      */
/*            using integer arithmetics only     */
/*                                               */
/*            based on "piqpr8.c" (2006-09-08)   */
/*            wrote by David H. Bailey           */
/*                                               */
/* 2012/10/18 Created by pcm1723                 */
/*************************************************/
/* for ATtiny13A                                 */
/*************************************************/

#define USE_AVR    (1)
#define USE_SUART  (1)
#define USE_ASM    (1)
#define PRINTLN    xmit_nl

// internal clock calibration flag
//#define CLK_CALIB  (1)

#include <stdint.h>
#include "suart.h"

// BBP algorithm core
#include "pi_BBP.pde"

#include <avr/io.h>

// transmit 2 hex digits
void xmit_hex2(uint8_t d)
{
  xmit_hex1(d >> 4);
  xmit_hex1(d);
} // void xmit_hex2()

// transmit 4 hex digits and a space
void xmit_hex4s(uint16_t d)
{
  xmit_hex2(d >> 8);
  xmit_hex2(d);
  xmit(' ');
} // void xmit_hex4s()

void main(void)
{
  PORTB |= _BV(PORTB1); // hold TxD (PB1/MISO) "High" for idle
  DDRB  |= _BV(DDB1);   // set output direction for PB1/MISO
#if !defined(CLK_CALIB)
  xmit_nl();
  xmit('3'); // integer part
  xmit('.');
  setup();   // calculate pi
  xmit_nl();
#else // internal clock oscillator calibration
// typical (38.4 / 2) = 19.2 [kHz] square wave output from PB1/MISO/OC0B (pin 6)
  TCCR0A = (_BV(COM0B0) | _BV(WGM01)); // OC0B toggle, CTC mode
  TCCR0B = (_BV(CS00)); // CTC mode, 1/1 CLK
  OCR0A  = 250 - 1;     // 9.6 [MHz] / 250 = 38.4 [kHz]
  OCR0B  = 0;
#endif
}

そして、下の 2 つのアセンブラのソース・ファイル (サフィックス「.S」) も同じフォルダに置き、これもアセンブル対象となるようにプロジェクトに追加します。
「suart.S」

;
; Modified for CLK = 9.6 MHz (ATtiny13A)
; and omit rcvr() func 
; and add xmit_nl(), xmit_hex1() func
;
; 2012/10/16 pcm1723
;
;---------------------------------------------------------------------------;
; Software implemented UART module                                          ;
; (C)ChaN, 2005 (http://elm-chan.org/)                                      ;
;---------------------------------------------------------------------------;
; Bit rate settings:
;
;            1MHz  2MHz  4MHz  6MHz  8MHz  9.6MHz  10MHz  12MHz  16MHz  20MHz
;   2.4kbps   138     -     -     -     -       -      -      -      -      -
;   4.8kbps    68   138     -     -     -       -      -      -      -      -
;   9.6kbps    33    68   138   208     -       -      -      -      -      -
;  19.2kbps     -    33    68   102   138     166    173    208      -      -
;  38.4kbps     -     -    33    50    68      82     85    102    138    172
;  57.6kbps     -     -    21    33    44      54     56     68     91    114
; 115.2kbps     -     -     -     -    21      26     27     33     44     56

.nolist
#include <avr/io.h>
.list

#define	BPS	82	/* Bit delay. for 38.4 kbps, clk = 9.6 MHz, (see above table) */
;#define	BPS	77	/* Bit delay. for 38.4 kbps, clk = 9.06 MHz (-5.6%) */

#define	BIDIR	0	/* 0:Separated Tx/Rx, 1:Shared Tx/Rx */

 
; for ATtiny13A (8-PDIP)
;
; PB1/MISO (pin 6) for TxD
; PB0/MOSI (pin 5) for RxD

#define	OUT_1		sbi _SFR_IO_ADDR(PORTB), 1	/* Output 1 */
#define	OUT_0		cbi _SFR_IO_ADDR(PORTB), 1	/* Output 0 */
#define	SKIP_IN_1	sbic _SFR_IO_ADDR(PINB), 0	/* Skip if 1 */
#define	SKIP_IN_0	sbis _SFR_IO_ADDR(PINB), 0	/* Skip if 0 */



#ifdef SPM_PAGESIZE
.macro	_LPMI	reg
	lpm	\reg, Z+
.endm
.macro	_MOVW	dh,dl, sh,sl
	movw	\dl, \sl
.endm
#else
.macro	_LPMI	reg
	lpm
	mov	\reg, r0
	adiw	ZL, 1
.endm
.macro	_MOVW	dh,dl, sh,sl
	mov	\dl, \sl
	mov	\dh, \sh
.endm
#endif

;---------------------------------------------------------------------------;
;
; Prototype: void xmit_nl( void );
.global xmit_nl
.func   xmit_nl
xmit_nl:
    ldi     r24,0x0D    ; CR
    rcall   xmit        ; transmit
    ldi     r24,0x0A    ; LF
    rjmp    xmit        ; transmit
.endfunc

;---------------------------------------------------------------------------;
; Transmit a hex digit in lower nibble of R24
;
; Prototype : 
;
; void xmit_hex1(uint8_t d) {
;   d &= 0x0F;
;   xmit( (9 < d) ? ('A' + d - 10) : ('0' + d));
; }
;

        .global xmit_hex1
        .func   xmit_hex1
xmit_hex1:
        andi	r24, 0x0F	; extract lower nibble
        cpi 	r24, 10	    ; 10
        brcs	1f      	; branch if (10 > r24)
        subi	r24, -('A' - '0' - 10)
1:
        subi	r24, -('0')
        .endfunc

;
; fall into xmit()
;

;---------------------------------------------------------------------------;
; Transmit a byte in serial format of N81
;
;Prototype: void xmit (uint8_t data);
;Size: 16 words

.global xmit
.func xmit
xmit:
#if BIDIR
	ldi	r23, BPS-1	;Pre-idle time for bidirectional data line
5:	dec	r23     	;
	brne	5b		;/
#endif
	in	r0, _SFR_IO_ADDR(SREG)	;Save flags

	com	r24		;C = start bit
	ldi	r25, 10		;Bit counter
	cli			;Start critical section

1:	ldi	r23, BPS-1	;----- Bit transferring loop 
2:	dec	r23     	;Wait for a bit time
	brne	2b		;/
	brcs	3f		;MISO = bit to be sent
	OUT_1			;
3:	brcc	4f		;
	OUT_0			;/
4:	lsr	r24     	;Get next bit into C
	dec	r25     	;All bits sent?
	brne	1b	     	;  no, coutinue

	out	_SFR_IO_ADDR(SREG), r0	;End of critical section
	ret
.endfunc


#if (0)
;---------------------------------------------------------------------------;
; Receive a byte
;
;Prototype: uint8_t rcvr (void);
;Size: 19 words

.global rcvr
.func rcvr
rcvr:
	in	r0, _SFR_IO_ADDR(SREG)	;Save flags

	ldi	r24, 0x80	;Receiving shift reg
	cli			;Start critical section

1:	SKIP_IN_1		;Wait for idle
	rjmp	1b
2:	SKIP_IN_0		;Wait for start bit
	rjmp	2b
	ldi	r25, BPS/2	;Wait for half bit time
3:	dec	r25
	brne	3b

4:	ldi	r25, BPS	;----- Bit receiving loop
5:	dec	r25     	;Wait for a bit time
	brne	5b		;/
	lsr	r24     	;Next bit
	SKIP_IN_0		;Get a data bit into r24.7
	ori	r24, 0x80
	brcc	4b	     	;All bits received?  no, continue

	out	_SFR_IO_ADDR(SREG), r0	;End of critical section
	ret
.endfunc
#endif 

「mulmod.S」

.nolist
#include <avr/io.h>
.list
;---------------------------------------------------------------------------;
;
; Prototype : 
;
; uint16_t mulmod(uint16_t a, uint16_t b, uint16_t ak);
; argument:          R25:R24,    R23:R22,     R21:R20
;
; function result:   R25:R24
;
; local variable :   R19:R18
;

        .global mulmod
        .func   mulmod
mulmod:
1:
;   while (a >= ak) {
        rjmp	3f      	;
2:
;    a -= ak;
        sub     r24, r20
        sbc 	r25, r21    ; a = a - ak
3:
;   } // while(a >= ak) { ...
        cp  	r24, r20
        cpc 	r25, r21    ; a - ak
        brsh	2b         	; branch if SAME or HIGHER
;   r = 0;
        ldi 	r18, 0x00	; 
        ldi 	r19, 0x00	; r = 0
;   while (b) { 
        rjmp	7f     	;

4:
;     if (0x01 & b) {
        sbrs	r22, 0  ; skip if (1 == b0)
        rjmp	5f     	; jump if (0 == b0)
;     r += a;
        add 	r18, r24
        adc 	r19, r25    ; r = r + a
        brcs    45f     ; branch if carry occurred
;     if (r >= ak) {
        cp  	r18, r20    ;
        cpc 	r19, r21    ; r - ak
        brlo	5f      	; branch if LOWER
;       r -= ak;
45:
        sub  	r18, r20
        sbc 	r19, r21    ; r = r - ak
5:
;     } // if (r >= ak) ...
;   } // if (0x01 & b) ...
;   a <<= 1;
        lsl 	r24
        rol 	r25
        brcs    55f     ; branch if carry occurred
;   if (a >= ak) { 
        cp  	r24, r20
        cpc 	r25, r21    ; a - ak
        brlo	6f      	; branch if LOWER
;     a -= ak;
55:
        sub 	r24, r20
        sbc 	r25, r21    ; a = a - ak
6:
;   } // if (a >= ak) ...
;   b >>= 1;
        lsr 	r23
        ror 	r22     ; b = b / 2
7:
;   } // while (b) { ...
        cp  	r22, r1
        cpc 	r23, r1 ; b - 0
        brne	4b      ; branch if (0 != b)
;   return(r);
        movw	r24, r18
        ret
        .endfunc

また、下のヘッダ・ファイルも同じソースフォルダに置きます。
これはヘッダ・ファイルなのでプロジェクトのコンパイル対象として登録する必要はありません。
「suart.h」

#ifndef SUART
#define SUART

void xmit(uint8_t);
uint8_t rcvr();

// transmit newline (CR + LF)
void xmit_nl( void );

// transmit 1 hex digit
void xmit_hex1(uint8_t c);

// transmit 4 hex digits and a space
void xmit_hex4s(uint16_t d);

#endif	/* SUART */

さらに、前回の「pi_BBP.pde」も同じフォルダに置きます。
これもインクルードして使われるので登録する必要はありません。
「suart.S」、「suart.h」は ChaN さんの web サイトのページ

ELM - AVR ライタの製作(4種)

の一番下の方にある
ISP通信サンプル、汎用AVRモニタ(GCC)」
のリンクからダウンロードできるソフトウェア UART プログラムに手を加えたものです。

  • 受信プログラムをオミット
  • CPU クロック 9.6 MHz に対応
  • CR/LF 出力、16 進 1 桁出力ルーチンの追加

等の変更が加えてあります。
「pi_BBP.c」の

  #define CLK_CALIB  (1)

の部分のコメントを外して「CLK_CALIB」が定義されるようにしてコンパイルしたオブジェクトを書き込んで動作させると、内蔵カウンタにより CPU クロックを 500 分周してシリアル出力 (6 番ピン) から出力するモードになります。
内蔵クロック 9.6 MHz を 500 分周すると 19.2 kHz の方形波となり、これをシリアル出力として見ると、38.4 kbps で ASCII 文字 'U' (0x55) を連続的に送信しているのと等価になります。
6 番ピンから出力される信号の周波数を測定して 500 倍すれば、CPU クロックの周波数を推定できます。
本来は、OSCCAL レジスタに書き込む値を調整して、内蔵クロック発振周波数自体を 9.6 MHz に近づけるのがスジですが、プログラム的に余裕がないので、「suart.S」内の

#define	BPS	82	/* Bit delay. for 38.4 kbps, clk = 9.6 MHz, (see above table) */
;#define	BPS	77	/* Bit delay. for 38.4 kbps, clk = 9.06 MHz (-5.6%) */

のシンボル「BPS」の定数値そのものを変更します。
上でコメントアウトしてある 2 行目は手許にある ATtiny13A の Vcc = 3.3 V 時の実測値で、5.6 % ほど周波数が低かったので BPS の定数値も同じ割合で小さくしました。