' ++ ' ' Module Name: ' ' pi.bs2 ' ' Description: ' ' This module implements a program that computes PI to over 600 ' decimal places scrolling the resulting digits onto an LCD one by ' one as they are computed. The rate of display is quite slow at ' first but increases exponentially as digits are produced. If ' fewer digits or faster computation is desired, then change the ' value of the DIGITS constant below. ' ' Computing PI to 100 digits takes about 2 minutes. CPU time is ' proportional to the square of the number of digits: half as many ' digits runs four times faster while twice as many digits takes ' four times longer, etc. ' ' Once computed the result remains in EEPROM. Resetting the Stamp ' causes the computed value of PI to be rapidly redisplayed rather ' than slowly recalculated. ' ' If a speaker is connected to pin P2 a tone is generated as each ' digit is calculated. ' ' Implementation: ' ' The formula used to compute PI is: ' ' PI = 8 * atan(1/3) + 4 * atan(1/7) ' ' The formula used to compute arctangent is: ' ' N ' ***** ' * ' * (A*x)*((-1)**k) ' A * arctan(1/x) = * -------------------- ' * (2*k+1)*(x**(2*k+1)) ' * ' ***** ' k=0 ' ' Calculations are performed with multi-precision base 100 ' arithmetic using large EEPROM arrays to hold wide variables. ' Each array cell is one byte wide and thus holds two decimal ' digits. Two cells are allocated for the integer part leaving ' all the remaining cells for the fractional part. ' ' Environment: ' ' Parallax Basic Stamp II (BS2) ' ' Author: ' ' Tom Van Baak (tvb@LeapSecond.com) 21-Sep-1995 ' ' Revision History: ' ' -- ' ' Define constants. ' DIGITS con 700 ' number of decimal places to display ' (change to 50 for a quick demo) CELLS con DIGITS / 2 ' cells for displayed fraction LAG con 1 ' cells for ripple carry padding PAD con 2 ' cells to accommodate rounding error FSIZE con CELLS + PAD ' net fraction array size Lsd con 1 + FSIZE ' index of least significant cell LCD_PI con $F7 ' LCD character code for Greek pi ' ' Define EEPROM data. ' data word DIGITS ' number of digits (for picheck.bs2) Sreg data 0, 0, 0(FSIZE) ' sum of terms (initialized to 0.0) Areg data 0, 8, 0(FSIZE) ' first atan term (initialized to 8.0) Breg data 0, 4, 0(FSIZE) ' second atan term (initialized to 4.0) Treg data (2 + FSIZE) ' intermediate temp (uninitialized) ' ' Define variables. ' Msd var word ' most significant non-zero cell Half var bit ' high (1) or low (0) nibble of cell Index var word ' index used for array loops AregK var word ' saved k value for first term BregK var word ' saved k value for second term ' Call by reference parameters to arctangent function. K var word ' k value for current term X var nib ' x value for current term Xreg var word ' current term (equals Areg or Breg) ' Call by reference parameters to wide arithmetic functions. Left var word ' destination/source register parameter Right var word ' source register parameter Divisor var word ' divisor parameter for division ' Temporary variables required by wide arithmetic functions. Result var word Carry var word Temp var word LcdData var byte ' data byte to send to LCD ' -------------------------------------------------------- ' ' Compute PI to many decimal places. ' Start: ' ' Initialize the LCD and display "pi equals" prefix. ' gosub LcdInit for Index = 0 to 3 lookup Index, [ LCD_PI, " = " ], LcdData gosub LcdWrite next ' ' Start the main loop. At least one digit is calculated ' for each arctangent per loop. This allows digits of the ' arctangent sum to be printed live as they are calculated. ' AregK = 0 BregK = 0 for Msd = 1 to (CELLS + LAG) for Half = 1 to 0 Xreg = Areg X = 3 K = AregK gosub ArcTangent AregK = K Xreg = Breg X = 7 K = BregK gosub ArcTangent BregK = K ' ' Display digits some distance from the leading ' non-zero cell. This avoids a ripple carry from ' changing a digit that was already displayed. ' if Msd < LAG then NoPrint if (Msd = LAG) and (Half = 1) then NoPrint read Sreg + 1 + Msd - LAG, LcdData LcdData = (LcdData dig Half) + "0" gosub LcdDigit ' ' Display the decimal point between the integer ' and the fraction parts of the result. ' if (Msd > LAG) then NoPrint LcdData = "." gosub LcdDigit NoPrint: next next sleep 60 ' hold final display for a minute ' ' The following return restarts the program. Since Sreg ' now holds the computed value of PI, the program will ' redisplay that value rather than re-computing PI again. ' The same redisplay occurs when the Stamp is reset (with ' reset button or power on). ' ' N.B. If the Stamp is prematurely reset (or powered off) ' in the middle of computing PI, then the redisplayed ' value will be incomplete and wrong. ' ' If a cold restart is desired just re-download the program ' to the Stamp. ' return ' -------------------------------------------------------- ArcTangent: ' ' Routine Description: ' ' This routine calculates one additional digit of arctangent. ' ' Arguments: ' ' Xreg - Supplies the address of the A or B register. ' ' X - Supplies the reciprocal integer arctangent argument. ' ' K - Supplies the integer value for the current K index. ' Loop: read Xreg + Msd, Temp if Temp = 0 then DoRet if (Half = 1) and Temp < 10 then DoRet ' ' Handle 1 / X^(2k+1) product. ' Left = Xreg Right = Xreg Divisor = X * X if K <> 0 then DoDiv Divisor = X DoDiv: gosub WideDivide ' ' Handle 1 / (2k+1) product. ' Left = Treg Right = Xreg Divisor = 2 * K + 1 gosub WideDivide ' ' Handle (-1)^k product and sum to final result. ' Left = Sreg Right = Treg if K & 1 then DoSub DoAdd: gosub WideAdd K = K + 1 goto Loop DoSub: gosub WideSubtract K = K + 1 goto Loop DoRet: return ' -------------------------------------------------------- WideAdd: ' ' Routine Description: ' ' This routine adds a wide number to a wide number: ' Left[] += Right[] ' ' Arguments: ' ' Left - Supplies the address of the destination register. ' ' Right - Supplies the address of the addend register. ' Carry = 0 for Index = Lsd to (Msd - 1) read Left + Index, Result read Right + Index, Temp Result = Result + Temp + Carry Carry = 0 if Result < 100 then AddOk Result = Result - 100 Carry = 1 AddOk: write Left + Index, Result next return ' -------------------------------------------------------- WideSubtract: ' ' Routine Description: ' ' This routine subtracts a wide number from a wide number: ' Left[] -= Right[] ' ' Arguments: ' ' Left - Supplies the address of the destination register. ' ' Right - Supplies the address of the subtrahend register. ' Carry = 0 for Index = Lsd to (Msd - 1) read Left + Index, Result read Right + Index, Temp Result = Result - Temp - Carry Carry = 0 if Result < 100 then SubOk Result = Result + 100 Carry = 1 SubOk: write Left + Index, Result next return ' -------------------------------------------------------- WideDivide: ' ' Routine Description: ' ' This routine divides a wide number by a word: ' Left[] = Right[] / Divisor ' ' Arguments: ' ' Left - Supplies the address of the destination register. ' ' Right - Supplies the address of the dividend register. ' ' Divisor - Supplies the divisor word value. ' ' N.B. 16-bit unsigned arithmetic overflows are avoided by manually ' checking for the case where (Carry*100 + 99) would exceed ' 65535. In this case the division is performed in two base ' ten steps increasing the maximum valid Divisor to 6543. ' Carry = 0 for Index = Msd to Lsd read Right + Index, Result if Carry > 654 then Large Small: Temp = (Carry * 100) + Result Result = Temp / Divisor Carry = Temp // Divisor goto DivOk Large: Carry = Carry * 10 Temp = ((Carry // Divisor) * 10) + Result Result = ((Carry / Divisor) * 10) + (Temp / Divisor) Carry = Temp // Divisor DivOk: write Left + Index, Result next return ' -------------------------------------------------------- ' ' LCD/Stamp connection diagram: ' ' LCD Stamp II ' ------------ ------------ ' pin 24 - PWR (e.g., 9V) ' pin 1 - Vss .............. pin 23 - GND ' pin 2 - Vdd .............. pin 21 - +5V ' pin 3 - Vo ... (10k pot) ' pin 4 - RS .............. pin 5 - P0 ' pin 5 - R/W ... (gnd) ' pin 6 - E .............. pin 6 - P1 ' pin 7 - DB0 ... (gnd) pin 7 - P2 (-||- speaker) ' pin 8 - DB1 ... (gnd) ' pin 9 - DB2 ... (gnd) ' pin 10 - DB3 ... (gnd) ' pin 11 - DB4 .............. pin 9 - P4 ' pin 12 - DB5 .............. pin 10 - P5 ' pin 13 - DB6 .............. pin 11 - P6 ' pin 14 - DB7 .............. pin 12 - P7 ' ' ' Define Stamp IO ports. ' RS con 0 ' register select (LCD pin) E con 1 ' enable (LCD pin) SPEAKER con 2 ' speaker pin (optional) ' ' LCD display initialization commands. ' LCD8 con %10000000 ' set DD RAM address = 0 LCD7 con %1000000 ' set CG RAM address = 0 LCD6 con %100000 ' mode: 4-bit, one line, 5x7 dots, *, * LCD5 con %11000 ' shift display, left, *, * LCD4 con %1100 ' display on, cursor off, blink off LCD3 con %111 ' set entry mode: shift left LCD2 con %10 ' home, * LCD1 con %1 ' clear ' ' LCD Initialization for Hitachi HD44780-based LCD units. ' LcdInit: ' ' Stamp port initialization. ' low E low RS ' select instruction register DirL = %11110111 pause 100 ' LCD power-on settle time ' ' LCD protocol initialization (4-bit mode). ' OutB = %0011 pulsout E, 1 pause 5 pulsout E, 1 pulsout E, 1 OutB = %0010 pulsout E, 1 ' ' LCD display initialization (for 1 x 16 display). ' for Index = 0 to 4 lookup Index, [ LCD1, LCD3, LCD4, LCD6, (LCD8 | 16) ], LcdData gosub LcdWrite next high RS ' select data register return ' ' LCD digit display. ' ' - Digits appear on the right and scroll off the left. ' - Digits are also echoed to PC via debug. ' - Each digit is accompanied by short speaker tone rising in ' pitch as successive digits are displayed (this delay also ' serves to throttle the speed with which PI is subsequently ' redisplayed). ' LENGTH con 250 ' tone duration in milliseconds LcdDigit: debug str LcdData\1 freqout SPEAKER, LENGTH, ((Msd * 2) - Half) * 8 + 500 ' fall into LcdWrite: OutB = LcdData.HighNib pulsout E, 1 OutB = LcdData.LowNib pulsout E, 1 return