*   Fuzzy Logic Inferrence Engine
*
*** Data structures and variables
*
 opt mex
    ORG $0000	    ;Beginning of HC11 RAM
CURRENT_INS RMB 8	;Storage for 8 8-bit inputs
FUZ_OUTS    RMB 32	;Storage for fuzzy outputs
COG_OUTS    RMB 4	;Defuzzified outputs
LOWEST_IF RMB	1	;Holds min grade of IF parts
SUM_OF_FUZ RMB	2	;11-bit sum of fuzzy outs
SUM_OF_PROD RMB 3	;19-bit sum of products
COGDEX	RMB 1	    ;Current out # for COG loop 0->4
SUMDEX	RMB 1	    ;Index for sum loop 8->0

    ORG $B600	    Beginning of HC11 EEPROM
IN_MF_PTRS FDB	IN0MF	    ;Addr of MF data for input 0
    FDB IN1MF	    ;Addr of MF data for input 1
    FDB IN2MF	    ;Addr of MF data for input 2
    FDB IN3MF	    ;Addr of MF data for input 3
    FDB IN4MF	    ;Addr of MF data for input 4
    FDB IN5MF	    ;Addr of MF data for input 5
    FDB IN6MF	    ;Addr of MF data for input 6
    FDB IN7MF	    ;Addr of MF data for input 7
*
* Input membership functions are defined by four 8-bit values per
* input label. Up to 8 labels per input so max size of MF data
* structure is 8*8*4=256 bytes. Unused labels take no space.
* Membership functions are trapezoids with the base greater than or
* equal to the top. Values are entered into this program as the
* X coordinates of 4 points but are stored as 2 points and 2 slopes.
*
***** MACRO Definition for input membership functions
*
INMF	MACR	;For input membership functions
    FCB \0			;First inflection point
    IFNE \1-\0			  ;Check for divide by zero
     FCB    ($FF+((\1-\0)/3))/(\1-\0) ;If not, calc slope
    ENDC
    IFEQ \1-\0			  ;Check for divide by zero
     FCB    $00 		    ;Indicates vertical slope
    ENDC
    FCB \2			;Third inflection point
    IFNE \3-\2			  ;Check for divide by zero
     FCB    ($FF+((\3-\2)/3))/(\3-\2) ;If not, calc slope
    ENDC
    IFEQ \3-\2			  ;Check for divide by zero
     FCB    $00 		    ;Indicates vertical slope
    ENDC
    ENDM
*
*****
INPUT_MFS EQU	*	   ;Input membership functions

IN0MF	EQU *		     ;(0) ROTATION
    INMF    0,0,0,8*4		 ;  (0) STOPPED
    INMF    0,8*4,$FF,$FF	 ;  (1) NOT_STOPPED

IN1MF	EQU *		     ;(1) EXAMPLE1
    INMF    $20,$40,$60,$A0	 ;  (0) TEST1

IN2MF	EQU *		     ;(2) TEMPERATURE
    INMF    2*0,2*0,2*40,2*50	   ;	(0) COLD
    INMF    2*40,2*50,2*60,2*70  ;  (1) COOL
    INMF    2*60,2*70,2*80,2*90  ;  (2) WARM
    INMF    2*80,2*90,2*95,2*110 ;  (3) HOT
    INMF    2*90,2*110,$FF,$FF	 ;  (4) VERY_HOT

IN3MF	EQU *		     ;(3) DAYS_SINCE_RAIN
    INMF    $00,$00,$00,$08    ;    (0) NONE
    INMF    $00,$10,$10,$18    ;    (1) SHORT
    INMF    $10,$20,$28,$40    ;    (2) MEDIUM
    INMF    $30,$40,$50,$60    ;    (3) LONG
    INMF    $50,$70,$FF,$FF    ;    (4) VERY_LONG

IN4MF	EQU *	    ;Not used
IN5MF	EQU *	    ;Not used
IN6MF	EQU *	    ;Not used
IN7MF	EQU *	    ;Not used

SGLTN_POS EQU	*	;Output singleton positions
OUT0MF	EQU *	    ;(0) WATERING_TIME
    FCB $00	    ;(0) CUT_OFF
    FCB $10	    ;(1) DECREASE_GREATLY
    FCB $40	    ;(2) DECREASE
    FCB $80	    ;(3) NORMAL
    FCB $B0	    ;(4) INCREASE
    FCB $F0	    ;(5) INCREASE_GREATLY
    FCB 0,0	;Unused; Must fill 8 per output
OUT1MF	FCB 0,0,0,0,0,0,0,0 ;Not used
OUT2MF	FCB 0,0,0,0,0,0,0,0 ;Not used
OUT3MF	FCB 0,0,0,0,0,0,0,0 ;Not used
*
* Each If part is of the form 00AA AXXX where AAA is a label (0-7)
* and XXX is an input (0-7). If parts are connected by ANDs. A rule
* may have any number of if parts (usually 2-8). Then parts are of
* the form 100Y YCCC where the MSB set indicates a then part, YY is
* the output number (0-3), and CCC is the output label (singleton
* 0-7). A rule may have any number of then parts (usually 1 or 2).
* A $FF indicates the end of a series of rules (number not limited).
*
*   FCB INx + 8*LABa	;If input XXX is label AAA
*   FCB $80+8*OUTy+LABc ;Then output YY is label CCC

RULE_START EQU	*	;Start of first rule
* Example rule: If TEMPERATURE is VERY_HOT and DAYS_SINCE_RAIN is LONG
* Then WATERING_TIME is INCREASE_GREATLY
RULE_1	FCB 2+(8*4),3+(8*3),$80+(8*0)+5
END_OF_RULE FCB $FF

***** Fuzzy Inferrence Engine Starts Here
*
    ORG $B66B
INFER_TOP LDX	#FUZ_OUTS   ;Point at first fuzzy output
    LDAA    #32     ;32 fuzzy outputs
CLR_OUTS    CLR 0,X	;Clear a fuzzy output
    INX 	;Point at next
    DECA	    ;Loop index
    BNE CLR_OUTS	;Continue till all fuzzy outs 0
    LDY #RULE_START ;Point to start of 1st rule
RULE_TOP    LDAA    #$FF	;Begin processing rule string
    STAA    LOWEST_IF	;Will hold grade of min if part
IF_LOOP LDAB	0,Y	;Get rule byte 00AA AXXX; If X is A
    BMI THEN_LOOP   ;If MSB=1, exit to then loop
    LDX #CURRENT_INS	;Point at current input data area
    ANDB    #$07	;Save only input number
    PSHB	    ;Will need input # again
    ABX 	;Point to specific input data
    LDAA    0,X     ;Get current input data
    LDX #IN_MF_PTRS ;Point at offsets for input MFs
    PULB	    ;Recover input number 0000 0XXX
    ABX 	;Point at pointer for this input #
    ABX 	;Point at pointer for this input #
    LDX 0,X	;Get pointer into MF data area
    LDAB    0,Y     ;Get rule if part 00AA AXXX
    ANDB    #$38	;00AA A000 is 8 times AAA
    LSRB	    ;000A AA00 4 times AAA
    ABX 	;X points at MF points & slopes
    CMPA    0,X     ;Compare input data to MF pt1
    BHS NOT_SEG0	;Branch if not segment zero
    CLRB	    ;In seg 0 grade is zero
    BRA HAVE_GRADE  ;Have grade of membership
NOT_SEG0    CMPA    2,X     ;Compare input data to MF pt2
    BHI IS_SEG2     ;Branch if segment two
    LDAB    1,X     ;Slope1 -> B
    BEQ JAM_FF	    ;If vert slope, jam $FF
    SUBA    0,X     ;Input value - pt1 -> A
    MUL 	;Grade in B if D < $100
    CPD #$100	    ;Check for overflow
    BLO HAVE_GRADE  ;If < $100 grade OK in B
JAM_FF	LDAB	#$FF	    ;Else limit B to $FF
    BRA HAVE_GRADE  ;Have grade of membership
IS_SEG2 LDAB	3,X	;Slope2 -> B
    SUBA    2,X     ;Input value - pt2 -> A
    MUL 	;Grade in B if D < $100
    CPD #$100	    ;Check for overflow
    BLO B_OK	    ;If < $100 value in B OK
    LDAB    #$FF	;Else limit B to $FF
B_OK	LDAA	#$FF	    ;Grade should be $FF - (B)
    SBA 	;Grade of membership in B
HAVE_GRADE CMPB LOWEST_IF   ;Is grade lowest so far ?
    BHS NOT_LOWR	;Branch if not lower
    STAB    LOWEST_IF	;If lower, replace lowest if
    BNE NOT_LOWR	;Skip ahead if not zero
FIND_THEN INY		;Adv rule pointer to then part
    LDAB    0,Y     ;Get next rule byte
    BPL FIND_THEN   ;MSB set means its a then part
FIND_IF INY	    ;Adv rule pointer to if part
    LDAB    0,Y     ;Get next rule byte
    BPL RULE_TOP	;MSB clear means its an if part
    CMPB    #$FF	;$FF is no more rules marker
    BNE FIND_IF     ;Continue looking for if or $FF
    BRA DEFUZ	    ;When all rules done, go defuzzify
NOT_LOWR    INY 	;Point to next rule byte
    BRA IF_LOOP     ;Continue for all if parts
THEN_LOOP LDX	#FUZ_OUTS   ;Point at fuzzy outputs
    ANDB    #$1F	;Save 8 times out # + label #
    ABX 	;X points at fuzzy output
    LDAA    LOWEST_IF	;Grade of membership for rule
    CMPA    0,X     ;Compare to fuzzy output
    BLO NOT_HIER	;Branch if not higher
    STAA    0,X     ;Grade is higher so update
NOT_HIER    INY 	;Point to next rule byte
    LDAB    0,Y     ;Get rule byte
    BMI CHK_END     ;If MSB=0 its a new rule
    JMP RULE_TOP	;Else process next rule byte
CHK_END CMPB	#$FF	    ;Check for end of rules flag
    BNE THEN_LOOP   ;If not $FF, must be a then part
DEFUZ	LDY #SGLTN_POS	;Point at 1st output singleton
    LDX #FUZ_OUTS   ;Point at 1st fuzzy output
    CLR COGDEX	    ;Loop index will run from 0->4
COG_LOOP    LDAB    #8	    ;8 fuzzy outs per COG output
    STAB    SUMDEX	;Inner loop runs 8->0
    LDD #$0000	    ;Used for quicker clears
    STD SUM_OF_FUZ  ;Sum of fuzzy outputs
    STD SUM_OF_PROD+1	;Low 16-bits of sum of products
    STAA    SUM_OF_PROD ;Upper 8-bits
SUM_LOOP    LDAB    0,X     ;Get a fuzzy output
    CLRA	    ;Clear upper 8-bits
    ADDD    SUM_OF_FUZ	;Add to sum of fuzzy outputs
    STD SUM_OF_FUZ  ;Update RAM variable
    LDAA    0,X     ;Get fuzzy output again
    LDAB    0,Y     ;Get Output singleton position
    MUL 	;Position times weight
    ADDD    SUM_OF_PROD+1   ;Low 16-bits of sum of products
    STD SUM_OF_PROD+1	;Update low 16-bits
    LDAA    SUM_OF_PROD ;Upper 8-bits
    ADCA    #0	    ;Add carry from 16-bit add
    STAA    SUM_OF_PROD ;Upper 8-bits of 24-bit sum
    INY 	;Point at next singleton pos.
    INX 	;Point at next fuzzy output
    DEC SUMDEX	    ;Inner loop index
    BNE SUM_LOOP	;For all labels this output
    PSHX	    ;Save index for now
    CLRA	    ;In case divide by zero
    LDX SUM_OF_FUZ  ;Demominator for divide
    BEQ SAV_OUT     ;Branch if denominator is 0
    TST SUM_OF_PROD ;See if more than 16-bit
    BNE NUM_BIG     ;If not zero, # is > 16-bits
    LDD SUM_OF_PROD+1	;Numerator for divide
    IDIV	    ;Result in low 8-bits of X
    XGDX	    ;Result now in B
    TBA 	;Move result to A
    BRA SAV_OUT     ;Go save output
NUM_BIG LDD SUM_OF_PROD ;Numerator upper 16 of 24-bit
    TST SUM_OF_PROD+2	;Check for rounding error
    BPL NO_ROUND	;If MSB clear, don't round
    ADDD    #1	    ;Round numerator up 1
NO_ROUND    FDIV	    ;D/X -> X, use upper 8 of 16
    XGDX	    ;Result now in A
SAV_OUT LDX #COG_OUTS	;Point to 1st defuz output
    LDAB    COGDEX	;Curent output number
    ABX 	;Point to correct output
    STAA    0,X     ;Update defuzzified output
    PULX	    ;Recover index
    INCB	    ;Increment loop index
    STAB    COGDEX	;Update
    CMPB    #4	    ;Done with all four outs?
    BNE COG_LOOP	;If not, continue loop

* Inference engine has completed one pass of all rules.

