'' ================================================================================================= '' '' File....... jmFreqIn with Multiply_Division_v02 '' Purpose.... To combine and test pasm freq counter '' and math routines together tachometer '' program. Recently added a freq generator. '' '' Started.... 04 MAR 2021 '' Updated.... 08 MAR 2021 '' '' Status: Program is inconsistent. '' '' Problems: '' 1). I am having acuracy problems that seem to '' be related to the PST, with baud issues. Slower '' baud rates, significantly increases problems '' with displaying the data. 115200 works ok,but '' with some errors that increase over time. Slow '' baud rates like 9600 create huge errors that display '' approximatly 1/2 the rpm value. '' '' Other PST related problems seem to be related to '' how fast or slow the main pasm routine repeats '' (or loops). '' '' Another PST related problem, seems to be related to '' how fast or slow the (spin) main method repeats '' its loops. '' '' 2). Curently Synth has a starting point of 50hz, but it '' displays ~1439 rpm which is almost half of 50hz, '' at ~24hz. Is it seems like waitpen or waitpeq is '' having problems (at least with synth), because using '' my electric motor/hall sensor test stand, and a high '' baud rate (115,200) the rpm output is close to normal. '' '' With the test stand the greatest errors occur with '' lower baud rates, like 9600. '' '' '' ToDo List: '' 1). Work on the accuracy problems that plague the '' pst. '' 2). Work on a software filter to average out '' rpm calculations. '' 3). Accuracy needs to be verified. We need a '' frequency generator to test the accuracy '' with. '' 4). Need a overflow and carry routine to support '' the pasm waitcnt instruction. '' '' Notes: '' 1). Added a 1/10th second delay in the bottom of '' the main pasm loop. '' '' 2). Added Frequency Generator "Synth" to program '' and set initial value of 50hz or 3,000rpm. '' '' 3). I increased the PST buffer from 64 to 256, but '' it did not help. '' '' 4). My original formulae for rpm was period = '' ((F_CPU/TIMER1_PRESCALE) * 60L) / period; //do reciprocal and scale. result = rpm/10 (sample freq *60 /10) '' ((80mhz/1)* 60)/ period = rpm '' (80mhz * 60)/period = rpm '' result = rpm/10, so that the user would not be '' overwhelmed by a constantly changing 1's column '' '' I modified that by splitting it into 3 pieces: '' tmp1 := (80mhz/10)*6, '' tmp2 := (phsa + phsb)/ 100 '' rpm := tmp1/tmp2, Approximatly 1000 to 9000rpm '' '' 5). 1 possible problem with this formulae is line 2 '' tmp2 := (phsa + phsb)/ 100, it creates a float? '' For example: phsa + phsb = 2743344 cycles / 100 '' = 27433.44, but I believe my multiplication routine '' will only return an integer (27433). '' '' '' ================================================================================================= CON _CLKMODE = XTAL1 + PLL16x _XINFREQ = 5_000_000 SensorInput_Pin = 0 ClockFrequency = 80_000_000 Pin = 16 ' Frequency = 40_000 'DC to 128MHz var '' For communication between SPIN and PASM code long par1D, par2P, parQ3 long stack[128] ' Stack space for Synth cog ' long Frequency '' par1D ClockFrequency (dividend) '' par2P SensorInput_Pin (pin) '' parQ3 is the final Quotient (rpm) obj pst : "Parallax Serial Terminal" ' Uses 1 cog ''freq : "Synth" ' Originally I called the object Synth. '' Then I loaded Synth into another cog PUB Main | rpm, rpmOld, Frequency Frequency := 50 ' 50hz for an initial 3000rpm test par1D := ClockFrequency ' Propeller clock freq for rpm calculation par2P := SensorInput_Pin ' For jm_freqin rpmOld := 999 ' Initialize value to non-zero number pst.start(115200) waitcnt(clkfreq * 10 + cnt) ' Wait 10 seconds, so that I can have my variable speed 'electric motor and hall sensor running, and the pst ' terminal up and runninig too cognew(Synth(Frequency), @stack[0]) ' I started a seperate cog to run as a frequency ' generator because I thought I had too many pieces ' of the program running on Cog_0, and that was ' contributing to my serial display problem. But it ' did not help. cognew(@entry, @par1D) ' The pasm object jm_freqin was one of the original ' parts of the program, and like the other pieces ' it was tested by itself before I added other supporting ' pieces of code to the program. '' Added a 1/10th second delay into the pasm '' routine jm_freqin to possibly fix a serial '' problem where the PST frezes after ~15 '' seconds. repeat rpm := long[@parQ3][0] ' Update the current par3 value if rpm <> rpmOld ' If par3 changes, allow it to print new value rpmOld := rpm ' Update the old par3 value. pst.newline pst.Str(string(" Rpm = ")) pst.Dec(rpm) ++Frequency ' Update/increase the frequency so that the Synth(Frequency) ' rpm value would increase as the program runs. '' ''PUB Synth(ctr_ab, Pin_, freq) | s, d, ctr, frq PUB Synth(freq) | s, d, ctr, ctr_ab, frq ' I changed the arguments when I started {{ ' jm_freqin as a seperate cog. ***************************************** * Frequency Synthesizer demo v1.1 * * Author: Beau Schwabe * * Copyright (c) 2007 Parallax * * See end of file for terms of use. * ***************************************** Original Author: Chip Gracey Modified by Beau Schwabe ***************************************** }} ctr_ab := "A" ' Pin_ := pin freq := freq #> 0 <# 128_000_000 'limit frequency range if freq < 500_000 'if 0 to 499_999 Hz, ctr := constant(%00100 << 26) '..set NCO mode s := 1 '..shift = 1 else 'if 500_000 to 128_000_000 Hz, ctr := constant(%00010 << 26) '..set PLL mode d := >|((freq - 1) / 1_000_000) 'determine PLLDIV s := 4 - d 'determine shift ctr |= d << 23 'set PLLDIV frq := fraction(freq, clkfreq, s) 'Compute frqa/frqb value ctr |= Pin 'set PINA to complete ctra/ctrb value if ctr_ab == "A" ctra := ctr 'set ctra frqa := frq 'set frqa dira[Pin]~~ 'make pin output if ctr_ab == "B" ctrb := ctr 'set ctrb frqb := frq 'set frqb dira[Pin]~~ 'make pin output PRI fraction(a, b, shift) : f if shift > 0 'if shift, pre-shift a or b left a <<= shift 'to maintain significant bits while if shift < 0 'insuring proper result b <<= -shift repeat 32 'perform long division of a/b f <<= 1 if a => b a -= b f++ a <<= 1 DAT '' ================================================================================================= '' '' File....... jm_freqin.spin '' Purpose.... '' Author..... Jon "JonnyMac" McPhalen (aka Jon Williams) '' Copyright (c) 2009 Jon McPhalen '' -- see below for terms of use '' E-mail..... jon@jonmcphalen.com '' Started.... 09 JUL 2009 '' Updated.... 10 JUL 2009 '' '' ================================================================================================= '' When recording clock tics of a low signal (NEG Detect), '' use "waitpeq" by waiting for pin == 1. That way the '' low signal has completed and has transitioned to the '' high signal. After the waitpeq transition, the phsb '' register is ready to be read. '' When recording clock tics of a high signal (POS Detect), '' use "waitpne" by waiting for pin == 0. That way the '' high signal has completed and has transitioned to the '' low signal. After the waitpne transition, the phsa '' register is ready to be read. '--------------------------------------------------------------------------------------------------------- '' Pass data too and from Spin ' con1_d1 long 10 ' Constant used in rpm calculation ' con2_m1 long 6 ' Constant used in rpm calculation ' con3_d2 long 100 ' Constant used in rpm calculation entry org 0 '' Pull down Spin constants '' through par register mov hub, par ' Get HUB memory address of par mov par1_Addr, hub ' Store this address in PAR1 equivilant rdlong clkfreq_, par1_Addr ' Load arg1D_d1 as (clkfreq) add hub, #4 ' hub now points to "par2" in HUB mov par2_Addr, hub ' Store this address in PAR2 equivilant rdlong pin_, par2_Addr ' Load arg1D_d2 as (clock cycles from par) add hub, #4 ' hub now points to "par3" in HUB mov par3_Addr, hub ' Store this address to return the result FrequencyCounter ' mov tmp1, par ' Copy the address into tmp1 ' rdlong tmp2, tmp1 ' Read the address and copy the variable ' into tmp2. Tmp2 now holds the pin_ number. mov ctra, pos_detect ' Set CounterA to measures high phase add ctra, pin_ ' Set CtrA to use the FrequencyPin mov frqa, #1 ' Set CtrA to record every clockCycle mov ctrb, neg_detect ' Set CounterB to measures low phase add ctrb, pin_ ' Set CtrB to use the FrequencyPin mov frqb, #1 ' Set CtrB to record every clockCycle mov mask, #1 ' create pin_ mask equal to %00000000_00000000_00000000_00000001 shl mask, pin_ ' Shift the high bit into the proper location ' %00000000_00000000_00000000_00000001 << Input pin_ Number andn dira, mask ' Set pin_ as an input in this cog ' add tmp1, #4 ' Copy the next long address into tmp1 ' mov cyclepntr, tmp1 ' save address of Par + 4 into cyclepntrhub for ' Passing data back to Spin program. restart waitpne mask, mask ' Pause a cogs execution until I/O pin_(s) does not '' Program coninusly loops '' at restart and resets ' match designated state(s)(Input pin_ = 1). '' Counters to 0 Waitpen is waiting for a low phase by watching INA ' mask AND mask == 1 on the Input pin_ ' The opposite of of Input pin_ = 1 is Input pin_ = 0 ' and then waitpen allows counter to record. mov phsa, #0 ' clear high phase counter highphase waitpeq mask, mask ' Pause a cogs execution until I/O pin_(s) match ' designated state(s),Wait for pin_ == 1 ' Waitpeq is waiting for a low phase by watching INA ' mask AND mask == 1 on the Input pin_ ' Since Input pin_ = 1 is what waitpeq is waiting for ' it allows counter to record. mov phsb, #0 ' clear low phase counter waitpne mask, mask ' wait for pin_ == 0, ' INA input pin_ is 0 AND mask is 1 = 0 mov cycles, phsa ' capture high phase cycles lowphase waitpeq mask, mask ' let low phase finish add cycles, phsb ' add low phase cycles ++ '' 'cycles' now holds total clock cycles '' for 1 revolution '' Add error trap, to catch a rollover 'endcycle ' wrlong cycles, cyclepntr ' update PAR hub address '' Now pass cycles to line2 dividend '---------------------------------┌──────────────────────────────────┐----------------------------------------- '---------------------------------│ Line 1 division (clkfreq / 10) │---------------------------------------- '---------------------------------└──────────────────────────────────┘---------------------------------------- ' quotient to spin. '' Pasm main routine '------------------------------------------------------------------------- '' Pass clkfreq to division1 as dividend and call division_1 '------------------------------------------------------------------------- '' arg1 dividend '' arg2 divisor '' result quotient '' tmp1 temporary working register '' tmp2 temporary working register '' con1_d1 Constant (10) used in line1 calculation mov arg1D_d1, clkfreq_ ' Store clkfreq into arg1D dividend mov arg2D_d1, con1_d1 ' Load constant #10 into arg2D divisor call #division ' Call division subroutine '---------------------------------┌──────────────────────────────────────┐----------------------------------------- '---------------------------------│ Line 1 Mutiplication (quotient * 6) │---------------------------------------- '---------------------------------└──────────────────────────────────────┘---------------------------------------- '------------------------------------------------------------------------- '' Pass result1D (quotient) from division1 and pass con2_m1 (6) '' into multiply1 and save the line1_Result '------------------------------------------------------------------------- '' arg1 Multiplicand '' arg2 Multiplier '' result product '' tmp1 temporary working register '' tmp2 temporary working register '' con2_m1 Constant (6) used in line1 calculation mov arg1M_m1, result1D_d1 ' Copy the result of the previous calc into the Multiplicand mov arg2M_m1, con2_m1 ' Copy a constant (6) into the Multipier call #multiply ' Call line 1 sub-routine '---------------------------------┌─────────────────────────────────────────┐----------------------------------------- '---------------------------------│ Line 2 division ((phsa + phsb) / 100) │---------------------------------------- '---------------------------------└─────────────────────────────────────────┘---------------------------------------- '------------------------------------------------------------------------- '' Pass (phsa + phsb) to division_2 as dividend and call division_2 '' And save quotient in result1D_d2 '------------------------------------------------------------------------- '' arg1 dividend '' arg2 divisor '' result quotient '' tmp1 temporary working register '' tmp2 temporary working register '' con3_d2 Constant (100) used in line2 calculation mov arg1D_d1, cycles ' Load arg1D_d2 as (clock cycles from freqIn) mov arg2D_d1, con3_d2 ' Load constant #3 into line2 calc call #division ' Call line 2 sub-routine '----------------------┌─────────────────────────────────────────┐---------------------------------------------------- '----------------------│ Line 3 division RPM := (line 1 / line 2)│--------------------------------------------------- '----------------------└─────────────────────────────────────────┘--------------------------------------------------- '------------------------------------------------------------------------- '' Pass results from line 1 (dividend) and line 2 result1D_d2(divisor) '' into division_3 '------------------------------------------------------------------------- '' arg1 dividend '' arg2 divisor '' result quotient '' tmp1 temporary working register '' tmp2 temporary working register mov arg1D_d1, vResultM ' Copy result from line 1 mov arg2D_d1, result1D_d1 ' Copy result from line 2 call #division ' Call line 3 sub-routine '------------------------------------------------------------------------- '' Pass results from line3 division3 and save into par3 '' to be displayed in spin '------------------------------------------------------------------------- wrlong result1D_d1, par3_Addr ' Write result to spin (rpm) '' Added a 1/10th second delay '' It needs overflow protection mov time, cnt ' move counter value into time add time, #9 ' add the number 9 to the time variable to ' skip over time delay before using value waitcnt time, delay ' loop is the marker for this line '' Return from division sub routine and '' fall into infinite loop. jmp #restart ' infinite loop. {{ :infinite_loop nop ' created a single pass infinite loop nop ' that solved some of my early pst nop ' serial display problems jmp #:infinite_loop }} '------------------- End of Main ------------------------------------------------------ '========================================================================= '========== Unsigned 32-bit ultrafast Kenyan integer division ============ '========================================================================= ' Arguments: 32-bit dividend and divisor ' Results: 32-bit quotient and remainder '------------------------ division 'Prepare temporary working registers mov tmp1D_d1, #0 'Clear 32-bit quotient mov tmp2D_d1, #1 'Prepare "divide back" loop cntr 'First round of Kenyan division: double divisor repeatedly until it 'becomes larger then dividend. Count the number of doublings :first_loop shl arg2D_d1, #1 'Double divisor cmp arg2D_d1, arg1D_d1 wz, wc 'Compare doubled divisor w dividend 'If new divisor smaller or equal, 'C is set, when equals Z is set if_c_and_nz add tmp2D_d1, #1 'if_c_and_nz increment counter if_c_and_nz jmp #:first_loop 'IF_C repeat first_loop 'If new divisor same as dividend, Z flag is rised, then the result is 'some power of two. Calculate it and finish if_z mov tmp1D_d1, #1 if_z rol tmp1D_d1, tmp2D_d1 if_z mov arg1D_d1, #0 if_z jmp #:finish 'Second Round of Kenyan division: Half back divisor and double quotient 'repeatedly and subtract divisor from dividend when dividend is larger 'or equal of divisor. In this case increment quotient with one, after 'doubling :second_loop shr arg2D_d1, #1 'Half divisor cmpsub arg1D_d1, arg2D_d1 wc, wz 'Compare dividend with divisor, 'If dividend is greater or equal 'than divisor, then subtract 'divisor from dividend and set C rcl tmp1D_d1, #1 'Double quotient and rotate C into 'LSB of quotient if_nz djnz tmp2D_d1, #:second_loop 'Continue division if something 'remained and some steps are left if_z sub tmp2D_d1, #1 'Remainder is zero, no more "half" if_z rol tmp1D_d1, tmp2D_d1 'steps needed. Finish the job :finish ''Get result(s) mov result1D_d1, tmp1D_d1 ' Quotient Division_ret ret ''-------------------------- End Division sub routine #2 ----------------------------- '========================================================================= '========== Unsigned 32-bit lonesock integer multiplication ============ '========================================================================= {{ vRes (32-bit) := v1 (32-bit) * v2 (32-bit) ------------------------------------------ Break the multiplication of 2 32-bit numbers into 4 multiplications of the 4x 16-bit portions: v1 * v2 = (v1_hi * v2_hi) << 32 + (v1_hi * v2_lo) << 16 + (v1_lo * v2_hi) << 16 + (v1_lo * v2_lo) << 0 Note that the first term can not fit in our result so we ignore it, and I can re-combine v1_hi and v1_lo: v1 * v2 (fit into 32 bits) = (v1 * v2_lo) + (v1_lo * v2_hi) << 16 }} Multiply newmult '' setup mov vResultM, #0 ' Primary accumulator (and final result) mov tmp1M, arg1M_m1 ' Both my secondary accumulator, shl tmp1M, #16 ' and the lower 16 bits of arg1. mov tmp2M, arg2M_m1 ' This is the upper 16 bits of arg2, shr tmp2M, #16 ' which will sum into my 2nd accumulator. mov idx, #16 ' Instead of 4 instructions 32x, do 6 instructions 16x. :loop '' v1_hi_lo * v2_lo shr arg2M_m1, #1 wc ' get the low bit of arg2 if_c add vResultM, arg1M_m1 ' (conditionally) sum arg1 into my 1st accumulator shl arg1M_m1, #1 ' bit align arg1 for the next pass '' v1_lo * v2_hi shl tmp1M, #1 wc ' get the high bit of v1_lo, *AND* shift my 2nd accumulator if_c add tmp1M, tmp2M ' (conditionally) add v2_hi into the 2nd accumulator '' repeat 16x djnz idx, #:loop ' I can't think of a way to early exit this '' finalize shl tmp1M, #16 ' align my 2nd accumulator add vResultM, tmp1M ' and add its contribution '' vResultM becomes a global variable '' used in line 3 calc Multiply_ret ret ''---------------- End of multiply subroutine --------------------------- ''----------------- Constants & Variables ------------------------------------ con1_d1 long 10 ' Constant used in division calculation con2_m1 long 6 ' Constant used in multipication calculation con3_d2 long 100 ' Constant used in division calculation delay long 8_000_000 ' Waitcnt delta 1/10th of a second '------------------------------------------------------------------------- '-----Uninitialized COG registers used to store HUB memory addresses------ '------------------------------------------------------------------------- par1_Addr res 1 ' Read the Divedend for line 1 equation par2_Addr res 1 ' Read the Divedend for line 2 equation par3_Addr res 1 ' Write the Quotient from line 3 equation time res 1 ' Waitcnt variable '------------------------------------------------------------------------- '----------------Uninitialized COG registers used as variables------------ '------------------------------------------------------------------------- arg1D_d1 res 1 ' Argument for division subroutine arg2D_d1 res 1 ' Argument for division subroutine arg1M_m1 res 1 ' Argument for multipication subroutine arg2M_m1 res 1 ' Argument for multipication subroutine idx res 1 ' LoneSock multiplication index pin_ res 1 ' Frequency Input pin_ clkfreq_ res 1 ' Holds current Propeller Clock Frequency from Spin Constant result1D_d1 res 1 ' The quotient variable vResultM res 1 ' The product variable '------------------------------------------------------------------------- '---------Uninitialized COG registers used as working registers --------- '------------------------------------------------------------------------- hub res 1 ' Used as global hub long memory location tmp1D_d1 res 1 ' Temporary division working registers tmp2D_d1 res 1 ' Temporary division working registers tmp1M res 1 ' Temporary multipication working registers tmp2M res 1 ' Temporary multipication working registers ' -------------------------------------------------------------------------------------------------- '' jm_freqin constants and variables pos_detect long %01000 << 26 ' for ctra neg_detect long %01100 << 26 ' for ctrb ' tmp1 res 1 ' tmp2 res 1 mask res 1 ' mask for frequency input pin_ cyclepntr res 1 ' hub address of cycle count cycles res 1 ' cycles in input period fit 492