Announcement

Collapse
No announcement yet.

Floating point rutines in PBP

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Floating point rutines in PBP

    Hello

    I am trying to scale some measurements acquired in counts, such as the measurement variation will be between 0.000(%) to 100.000(%). The microcontroller is PIC16F877 at 20MHz. This is the device I have, and I will consider a newer approach (such as PIC16F887) after I will get this done.
    I am using the Microchip floating point routines (the file is fp.zip) for PBP, which are written in assembly. I did not find any detailed explain so I am posting here.

    These are the conditions:
    All measurements are acquired in counts. The lowest measurement I get is 22600 (this is low_count_span). The highest measurement is 46461 (high_count_span).
    This means that for 22600 counts I have to store 0.000 (low_%_span).
    For 46461 I must store 100.000 (high_%_span).
    It may happen for the measurements to go off-scale, this is why I need 100.000 and not strictly 99.999.
    These percentages must be stored, because later I have to transmit them to a 16-bit 4..20mA industrial line, to a serial line and to LCD (via lcdout).

    this means the math is this one:
    y[%] = [ (high_%_span - low_count_span) / high_count_span] * measured_counts + low_count_span

    for now I just test the numbers to see if the operation works:

    y[%] = [(100000 - 22600)/46461 ] * measured_counts + 22600


    Basically, I am trying to do this: y = [( A - B) / C ] * D + B and store "y" somewhere for later processing.
    A = 100000; B = 22600; C = 46461; D = measured counts (between 22600 and 46461).

    The code is not working and I am sure it is related to the big "100000" (in float it gets three more digits after).
    I am posting here because I studied the assembly routines in fp32.a16 which explains the principles: all the math results are stored in AARG which is accessed via aint. So far it is not working. I get only 65535.65535 on the LCD.

    Thank you very much for your patience.

    For first, this is what I tried so far and it is not working:

    Code:
    include "fp2032.bas"	' Include file for 14-bit core with RAM at $20 (32-bit)
    
      DEFINE LCD_DREG PORTA         
        DEFINE LCD_DBIT 0 ' 4 bit selected
        DEFINE LCD_RSREG PORTE
        DEFINE LCD_RSBIT 0 ' Reset Pin
        DEFINE LCD_EREG PORTE
        DEFINE LCD_EBIT 1 ' Enable Pin
        DEFINE LCD_LINES 4
        define LCD_DATAUS 250
    ; I need those for a 2-controller single LCD, 4 lines x 80 chars. the other ports are busy with SPI, i2c and 4..20mA outputs
    
    Define  __16F877   1    
    
    cinput con 100
    dinput con 1000
    
    ADCON1 = 7      	' Set PORTA and PORTE to digital. Otherwise no output to LCD.
    Low PORTE.2     	' activates first two rows on LCD. high -> activates rows 3 and 4
    lcdout $FE, 1      ; init LCD
    
    loop:
    
        aint = cinput  ; load 100
        gosub itofa    ; and convert it to float
    
        bint = dinput  ; load 1000
        gosub itofa    ; convert it to float
    
       gosub fpmul    ; multiply and get 100000  (later will be 100 with 6 decimals after)
       ;   result is transfered in aint
    
       bint = 22600  ; lowest measurement at initial calibration
       gosub itofb  ;   convert measurement to float
    
       gosub fpsub   ; in aint I get 100000 - 22600
    
       bint = 46461  ; this is the highest count I hope I will ever measure. Big trouble in the lab if this gets off-scale
       gosub itofb   ; convert that to float
    
       gosub fpdiv    ; at this time I have (100000 - 22600) / 46461
    
       bint = 30000   ; this is some "measured value from the process"
       gosub itofb     ; which I want to convert to percentages (%)
    
       gosub fpmul    ; at this time in aint I get [(100000 -22600) / 46462 ] * measured_counts
    
       bint = 22600  ;  store low span again
       gosub itofb   ; and convert it to float
    
       gosub fpadd  ;    aint <-  aint + bint
    
       fpplaces = 3  : gosub fpdisplayr      ; put the percentage result on LCD with three decimals
       pause 3000
    
       lcdout $FE,1 : lcdout $FE, 2    ;  clear and go to first line, first row
    
       goto loop     ; back to the zoo
    
    
      ; the rest of the code is not modified, according to 4func32.bas
    
    fpdisplayr:	If fpplaces=0 Then			' Set floating point barg to 0.5
    				bexp = $7E
    				bargb0 = $00
    				bargb1 = $00
    				bargb2 = $01
    			Endif
    			If fpplaces=1 Then			' Set floating point barg to 0.05
    				bexp = $7A
    				bargb0 = $4C
    				bargb1 = $CC
    				bargb2 = $CD
    			Endif
    			If fpplaces=2 Then			' Set floating point barg to 0.005
    				bexp = $77
    				bargb0 = $23
    				bargb1 = $D7
    				bargb2 = $0B
    			Endif
    			If fpplaces=3 Then			' Set floating point barg to 0.0005
    				bexp = $74
    				bargb0 = $03
    				bargb1 = $12
    				bargb2 = $6F
    			Endif
    			If fpplaces=4 Then			' Set floating point barg to 0.00005
    				bexp = $70
    				bargb0 = $51
    				bargb1 = $B7
    				bargb2 = $17
    			Endif
    
    			Gosub fpadd					' add barg to aarg
    
    fpdisplay:	bexp = aexp					' Store the FP value of aarg to the barg variables
    			bargb0 = aargb0
    			bargb1 = aargb1
    			bargb2 = aargb2
    
    			Gosub ftoia					' Convert aarg to integer
    			ahold = aint				' Save this value for the final display
    			
    			Gosub itofa					' Convert integer back to float
    
    			Swap aexp,bexp				' Swap the FP values of aarg and barg before subtraction
    			Swap aargb0,bargb0
    			Swap aargb1,bargb1
    			Swap aargb2,bargb2
    
    			Gosub fpsub					' Subtract the integer portion from the full number
    
    
    			bint = 10					' Make bint = 10 E fpplaces
    			If fpplaces=2 Then
    				bint = 100
    			Endif
    			If fpplaces=3 Then
    				bint = 1000
    			Endif
    			If fpplaces=4 Then
    				bint = 10000
    			Endif
    
    			bhold = bint				' Save the integer value of bint for zeros loop
    
    			Gosub itofb					' Convert bint to integer prior to FP multiply
    			Gosub fpmul					' Multiply the decimal portion x 10 E fpplaces
    
    			Gosub ftoia					' Convert result to aint integer
    
    
    			Lcdout dec ahold			' Display integer portion
    
    '			Serout2 spin,sbaud,[dec ahold]	' Use for 2400 bps serial output
    
    			If fpplaces > 0 Then					
    				Lcdout "."					' Display decimal point
    '				Serout2 spin,sbaud,["."]	' Use for 2400 bps serial output
    
    zeros:			bhold = bhold / 10							' Set bhold to be next place to right
    				If (aint < bhold) AND (bhold > 1) Then		' Check for leading zero in decimal
    					Lcdout "0"								' Display leading zero
    '					Serout2 spin,sbaud,["0"]				' Use for 2400 bps serial output
    					Goto zeros								' loop to check for another zero
    				Endif
    	
    				Lcdout dec aint			' Display the rest of the decimal portion
    
    '				Serout2 spin,sbaud,[dec aint]	' Use for 2400 bps serial output
    
    			Endif
    
    			aint = ahold				' Restore the original value of aint
    
    			Return
    Last edited by skaarj; 05-12-2014, 04:41 PM.

  • #2
    Originally posted by skaarj View Post
    this means the math is this one:
    y[%] = [ (high_%_span - low_count_span) / high_count_span] * measured_counts + low_count_span

    for now I just test the numbers to see if the operation works:
    y[%] = [(100000 - 22600)/46461 ] * measured_counts + 22600
    Well, to start off ... your formula is not correct.
    So you'll never end up with the right result.
    But it was a good attempt at using the FP routines. Even if the formula was wrong.

    ------------------------- ------------------------ -----------------------
    It should be ...
    ((high_%_span - low_%_span ) / (high_count_span - low_count_span)) * (Measured - low_count_span)

    ((100000 - 0) / (46461 - 22600)) * (Measured - 22600)

    Since ((100000 - 0) / (46461 - 22600)) are all constants that are known ahead of time ...
    They can be reduced to a single constant ... 4.190939189

    4.190939189 * (Measured - 22600)

    In floating point format, 4.190939189 = 0x81061C2D.
    Which makes the code much easier...
    Code:
    Measured  VAR WORD
    Measured = 30000      ; 22600 - 46461
    ...
        bexp = $81        ; 4.190939189
        bargb0 = $06
        bargb1 = $1C
        bargb2 = $2D
    
        AINT = Measured - 22600
        GOSUB ItoFA
        GOSUB FPMUL
    
        BINT = 1000         ; /1000 for % with 3 decimal places
        GOSUB ItoFB
        GOSUB FPDIV
        
        fpplaces = 3  : gosub fpdisplayr      ; put the percentage result on LCD
    Results:
    Code:
    22600 =   0.000
    30000 =  31.013
    46461 = 100.000
    PBP3 Manual : Microchip Datasheets - 10F, 12F, 16F, 18F
    Never download a PIC datasheet from anywhere but microchip.com

    Comment


    • #3
      OR ...

      Since it's using floating point numbers, you don't have to use 100000 for 100%, just use 100.
      This removes the need to divide by 1000 at the end.

      -------------------- ------------------------ --------------------- -----------------------
      ((high_%_span - low_%_span ) / (high_count_span - low_count_span)) * (Measured - low_count_span)

      ((100 - 0) / (46461 - 22600)) * (Measured - 22600)

      Now the constant changes
      ((100 - 0) / (46461 - 22600)) = 0.004190939

      0.004190939 * (Measured - 22600)

      In floating point format, 0.004190939 = 0x77095425.
      Which again makes the code much easier...
      Code:
      Measured  VAR WORD
      Measured = 30000      ; 22600 - 46461
      ...
          bexp = $77        ; 0.004190939
          bargb0 = $09
          bargb1 = $54
          bargb2 = $25
      
          AINT = Measured - 22600
          GOSUB ItoFA
          GOSUB FPMUL
          
          fpplaces = 3  : gosub fpdisplayr      ; put the percentage result on LCD
      Results are the same:
      Code:
      22600 =   0.000
      30000 =  31.013
      46461 = 100.000
      PBP3 Manual : Microchip Datasheets - 10F, 12F, 16F, 18F
      Never download a PIC datasheet from anywhere but microchip.com

      Comment


      • #4
        Thank you for the scale formula. Many nights with no sleep did their job in corrupting my memory.
        The sensor is for infrared spectroscopy and its measurements depend on its temperature and external pressure. If someone even blows on it, measurements change. Scale changes.
        So I thank you very much for the fast answer and for the help. Except all those constants are acquired during calibration time at system startup. Unfortunately, those numbers are no constants. That was the reason for all those successive operations I tried to do.

        I will keep trying.

        Comment


        • #5
          Back again

          It worked. It had to be you to light the bulb in my head.
          Now I must find how to store the percentage in some variable in order to use it for 16-bit 4..20mA analog output. If (of when) I find out how to do that, I'll be back here with the answer.


          I only included the important code. From the end loop, all the code is the same as above.
          It even displays percentages above the scale, cool!

          Questions:
          - what does "PM" mean? I searched the help topics, nothing found.
          Code:
          ' Define the target device for PM assembler
          Define  __16F877        1       ' Only use for PM, MPASM will define this by itself

          Test performed in Proteus.



          Thank you for enlighten me.

          Here is how it worked:


          Code:
              include "fp2032.bas"	' Include file for 14-bit core with RAM at $20 (32-bit)
          
              DEFINE OSC 20   
              DEFINE LCD_DREG PORTA         
              DEFINE LCD_DBIT 0 ' 4 bit selected
              DEFINE LCD_RSREG PORTE
              DEFINE LCD_RSBIT 0 ' Reset Pin
              DEFINE LCD_EREG PORTE
              DEFINE LCD_EBIT 1 ' Enable Pin
              DEFINE LCD_LINES 4
              define LCD_DATAUS 250
          
          
          Define  __16F877        1       
          
          fpplaces  var  byte			' Used to define number of decimal places in fpdisplay,fpdisplayr
          ahold    var   word			' Required by fpdisplay
          bhold    var   word			' Required by fpdisplay
          
          measured var word
          measured = 28000    ; for now this one is manually set.  later it will be measured in counts from the DAC.
          
          
          ;   percentage scale:  high_%_span and low_%_span
          high_p_span var byte : high_p_span = 100    ; warning:  scale is adjusted from the menu subrutine
          low_p_span var byte : low_p_span = 0        ; for testing purpose, at this time these are manually set
          
          
          high_count_span var word : high_count_span = 46461    ; warning:  these two will be obtained at calibration startup
          low_count_span var word : low_count_span = 22600   
          
          ; Math corrected by Darrel Taylor:  y% = ( (100 - 0) / (high_count_span - low_count_span) ) * (measured - low_count_span)
          
          xA var word : xa = 0      ; this one will be high_%_span - low_%_span
          xB var word : xB = 0      ; this is  high_count_span - low_count_span
          xC var word : xC = 0      ; this is measured - low_count_span
          
          ; the formula becomes:   Y% = ( xA / xB ) * C
          
          
          		ADCON1 = 7      	' Set PORTA and PORTE to digital
          		Low PORTE.2     	' select first half of the LCD (first two rows).
          					' HIGH porte.2 selects the other two rows
          		Pause 500       	' Wait for LCD to start
          		lcdout $FE, 1           ' init and clear
          		pause 500
          
          loop:
          
          	xA = high_p_span - low_p_span
          	xB = high_count_span - low_count_span
          	xC = measured - low_count_span
          
          	aint = xA        ; load A
          	gosub itofa    	 ; convert to float
          
          	bint = xB   ; load B
          	gosub itofb   ; convert it to float
          
          	gosub fpdiv    ; AINT = A / B
          
          	bint = xC     ; load C
          	gosub itofb   ; convert to float
          	
          	gosub fpmul     ;   AINT = AINT / C   or  AINT_final = (A/B) * C
          
          	LCDout $FE, 1
          	lcdout $FE,2, "counts: ", DEC measured, "= "
          
          	fpplaces = 3 : gosub fpdisplayr
          
          	pause 3000
          
          Goto loop			' back to the zoo

          Comment


          • #6
            This is what I found out about using the results somewhere else (for example - to drive a 4..20mA or a 0..10V output). To get the integer part and float part, I just have to look in the display function for the "LCDOUT"s and store those numbers somewhere.

            Trouble is that the values I am measuring do not increase in linear mode. They increase like a logarithm, so I do not get the correct results. This means that somehow I have to compensate by dividing the result with some natural logarithm function.

            Is there any picbasic implementation of the Microchip AN660 floating point routines? I'm trying to import the functions log(x) and pow(x,y). Still no results.

            The math16.inc specified in AN660 is different than the math16.inc in fp.zip, although it is specified:
            " ; RCS Header $Id: math16.inc 2.4 1997/02/11 16:58:49 F.J.Testa Exp $
            ; $Revision: 2.4 $"
            in both of them. And the math16.inc in AN660 has variable definitions at different addresses. If an application requires both AN576 and AN660, there will be some issues because the same variables are used in both supplied codes. It may be required for these two to be combined.
            Last edited by skaarj; 05-14-2014, 09:06 AM.

            Comment


            • #7
              Huh?
              That makes no sense to me.

              Why are you looking at values in the LCD routines?
              They destroy the value as it displays the digits.
              And everything is "Linear", you shouldn't need any LOG's.

              You already have the result 0-100% in floating point format.
              If I'm not mistaken, you want to scale that to a 16-bit integer (0-65535)

              The procedure is the same as the previous linear conversion, only the ranges are different.
              And this time ... they are definitely constants.

              ((65535 - 0) / (100 - 0)) * (Percent - 0)
              (65535 / 100) * Percent
              655.35 * Percent

              Code:
              AARGsave VAR BYTE[4]   
              
              ...
              
              	bint = xC     ; load C
              	gosub itofb   ; convert to float
              	
              	gosub fpmul     ;   AINT = AINT / C   or  AINT_final = (A/B) * C
              
              	LCDout $FE, 1
              	lcdout $FE,2, "counts: ", DEC measured, "= "
              
              	AARGsave(0) = AARGB2   ; Save AARG
              	AARGsave(1) = AARGB1
              	AARGsave(2) = AARGB0
              	AARGsave(3) = AEXP  
              
              	fpplaces = 3 : gosub fpdisplayr
              
              	AARGB2 = AARGsave(0)   ; restore AARG
              	AARGB1 = AARGsave(1)
              	AARGB0 = AARGsave(2)
              	AEXP = AARGsave(3)
                  
              	bexp = $88      ; * 655.35
              	bargb0 = $23
              	bargb1 = $D6
              	bargb2 = $66
              	GOSUB fpmul
              
              	bexp = $7E      ; + 0.5    rounding
              	bargb0 = $00
              	bargb1 = $00
              	bargb2 = $00
              	GOSUB fpadd
              	GOSUB FtoIA
              
              	LCDOUT $FE,$94,DEC AINT   ; display value (0-65535)
              And, "PM" was the old stand-alone assembler that only worked with 12-bit and standard 14-bit cores.
              It has been retired, and all programs should be assembled with MPASM now.
              PBP3 Manual : Microchip Datasheets - 10F, 12F, 16F, 18F
              Never download a PIC datasheet from anywhere but microchip.com

              Comment


              • #8
                The project I am working on is a spectrograph for measuring concentrations of different hydrocarbons. This is advanced PhD stuff. I am in a team of students, the professors here assigned me with the most crazy group I have ever seen, and I have to harvest their energy and put it to work. This happened last year when college started. I'm the only one who knows "some stuff" about microcontrollers so I'm coordinating the other colleagues. The project is made of 16 boards with 16 PIC16F877, each board having its own function:
                - Pulsing a vacuum tube, high power laser at adjustable periods of time (different frequencies for different hydrocarbon components);
                - manage a common data bus on which every board can input and output data;
                - measuring sensors outputs and separating each gas component;
                - calculate each component gas concentration;
                - one master boards control the others by injecting parameters on to the common bus (via a LCD menu interface);
                - each pair of my colleagues are responsible with one of the modules.

                Except for the PICs and LCDs, all the other electronics are golden microchips with Cyrillic encoding, recovered from old avionics with help of recycling companies. Here in Europe we can still get stuff like that. Their specifications are amazing and they are not documented on internet. AD / DA conversions on 16 and 32 bits in microseconds made by the soviet union in the 80's, capsules and pins covered in gold, that's serious stuff even for today standards. The system must react between laser pulses. Basically, the PICs are only coordinating the soviet controllers and extract data from them. Except for the pulsing stuff.

                Why such a complicated project? simple: they prepare us for emergency situation handling at well drilling sites in the middle of nowhere - to be able to diagnose and fix problems related to electronics and automation, to safely contain any possible bad situation until backup arrives, with any available components. Hard job to do, not for people with normal brains. If the well site is off-shore in the middle of the ocean - months pass between supplies and no time to waste if something happens - safety first, protection of nature, everything and everybody must stay safe. So our teachers teach us to think in low level. Even vacuum tubes are used in this project, in order to get the finest amplification possible with zero distortions.

                Mixture gases come from a well drilling site (we keep these in metal pressured bottles). We also have pure gases with known concentrations. One gas is released in a small chamber containing the sensor. The "laser board" pulses a huge laser into the chamber at high frequency, at this time another board reads sets of results (sinus voltages as counts) between the laser pulses, integrate them (surface-integration of a sinus wave, some of those golden soviet ADCs are really good at high speed) and passes the data (counts) to the common bus. Twelve of the boards process twelve gas components (methane, ethane, butane and so on), calculate some floating point operations, scale them to percentage (thank you for helping me with the scale formula)and display the results on LCD. We had an EMP experiment in another lab near ours. All PICs and LCDs died, we had to replace them. The soviet parts kept running (no more new settings received), except the laser which was no more pulsed.

                This morning I thought my brains went crazy when I inserted a calibration gas with 2.5% methane and 97.5% nitrogen. We have a cheap chinese concentration gas detector from an obscure company (Apolosense, China) which displays the correct concentration. With one decimal digit only. Well... my PIC on my LCD did not do that, it jumped to 16%. At high 99.99%, both displayed correctly. My LCD displayed with 4 decimal digits precision (99.8972%). Then my colleagues started to say the same about their gas components on other tests. Low concentration is not correct, high concentration is good. While scratching our heads, one of the chemist doctor girls said "If you will stop looking at my butt, stop thinking <<linear>> and take a look at the Beer-Lambert law relation, extract the concentration and look at the graphic, you will notice it's logarithmic. You want to scale it linear? Apply another logarithmic function and only after that you scale the result. Stupids!" Well, she is the math girl - she is smart with pen and paper and bad in electronics but we are gentlemen and do not think bad of her.

                I don't want to switch from PIC to a "smarter" microcontroller, we voted that nobody would have this privilege until the project ends. Or else there will be a riot.
                So this is why I am wondering about logarithms.

                I am sorry for this long story, and I hope you realized this is not some high school project. I am sorry my English helps me only with technical stuff, and less with literature. I am afraid to say something stupid. I read a lot of documentation where you did amazing things with microcontrollers (for example - DT interrupts, I found about that by reading on picbasic forum when I had trouble with timing the laser pulses). My madhouse asylum crazy engineering team is in love with sinclair spectrums, vacuum tubes and PDP mainframes with big open-reel-to-reel tape drives in the age of tablets and 4G services. Not ordinary people.

                The relation is [concentration] = ( -ln { 1 - [ (1 - Counts_I_active / (Counts_I_calibrated * Counts_I_reference) ) / counts_span ]} / a ) ^ (1/b)
                where all the variables are measured inside the process. And we scale the [concentration] in percentage and in 16-bit output with the formula you helped me. That is what our smart doctor girl said.

                So, again I am politely asking as an engineer to another engineer: is there any picbasic implementation of the Microchip AN660 floating point routines, expecially the log(x) (ln) and pow(x,y)?

                Thank you very much.
                Last edited by skaarj; 05-14-2014, 02:54 PM. Reason: Corrections in English grammar

                Comment


                • #9
                  I see, it's the initial measurements that are not linear.

                  Sorry, but there is currently no implementation of AN660 for PBP.

                  I'm sure they can be adapted, it's just that nobody's done it yet.
                  PBP3 Manual : Microchip Datasheets - 10F, 12F, 16F, 18F
                  Never download a PIC datasheet from anywhere but microchip.com

                  Comment


                  • #10
                    To the OP: Without wanting to detract from the thrill of working through the Microchip floating point routines, have you considered a fp hardware solution? In the past I have used http://micromegacorp.com/umfpu-v3.html and it worked well.

                    Comment

                    Working...
                    X