Fuzzy logic does real time on the DSP

Fuzzy logic doesn't require strange hardware or new programming languages, just a different approach to set membership. Plenty of physical systems, from elevators to boilers, can benefit from fuzzy-logic programming. This article explores progamming a commercial DSP chip to create a basic fuzzy-logic controller.

You can use standard off-the-shelf microprocessors to build fuzzy-logic systems. But what if you're designing a real-time, safety-critcal system? Fuzzy-logic controllers sometimes require more processing power to work in real time. Conventional microprocessors are adequate for most applications—even fuzzy-logic applications—but not always for safety-critical ones that require extremely fast and predictable response times. When conventional processors aren't fast enough, a digital signal processor (DSP) may be just what your system needs.

DSPs, the specialized microprocessors with math-oriented features and instruction sets, were initially used only in signal-processing applications. Now they've become more popular as the variety and quality of DSP software-development tools have increased. DSPs are now cost-competitive with general-purpose microprocessors. Today any application that can benefit from high-speed multiply/accumulate (MAC) operations is a candidate for DSP processors.

In this article, I'll describe the components of a fuzzy-logic system and give you an example of how to implement one using a DSP.

Fuzzy-logic basics
Lotfi Zadeh is considered the founder of fuzzy logic when in a 1965 paper he proposed an extension to Boolean set theory that was multi-valued rather than binary. His fuzzy-logic set theory is a generalization of classical set theory—a generalization that deals excellently with imprecision. The power of fuzzy logic is that it enables you to accurately describe a process or behavior without using mathematics.


Figure 1: Boolean variable for Tall


Figure 2: Fuzzy variable for Tall

Boolean sets are either true or false, while fuzzy sets can have partial membership. For example, Figure 1 shows a Boolean variable. In a Boolean table, Tall is true for six feet or greater and false for less than six feet. But the fuzzy variable Tall in Figure 2 is neither true nor false; it has varying degrees of membership. Boolean variables often are referred to as crisp sets , and fuzzy variables as fuzzy sets .

Fuzzy set behavior is similar to that of its crisp counterparts. For instance, fuzzy logic uses the AND, OR, NOT, and complement operators. AND-ing works by taking the lesser of the inputs. OR-ing takes the greater of the inputs; the complement operator works by taking one minus the value of the input.

Controller components
Any fuzzy-logic-based controller has three components: the fuzzifier, the rule base, and the defuzzifier. Each component plays an important role in transforming a crisp (digital) input to a fuzzy value, operating on that value, and then converting the fuzzy value back into a crisp output. Although implementation of fuzzy controllers differs, they all have these three basic components.

The fuzzifier takes a crisp input value and converts it to a fuzzy value by scaling the input value if necessary and transforming it into a multi-valued entity. Scaling maps the input's domain into some internal format that all variables use. This multi-valued entity is the result of comparing the input value to its corresponding input set and mapping the value to reflect its membership characteristics.

The rule base takes the input from the previous stage and adds up the areas for each relevant statement. The rule base is made up of a series of one or more IF-THEN statements. Each statement is in turn composed of two parts: the antecedent (to the left of the keyword “then”) and the consequence (to the right of “then”). A statement may have one or more antecedents and consequences. A typical rule statement looks like this:

if antecedent1 . . . antecedentN THEN
consequence1 . . . consequenceN

The antecedent and the consequence both take the form Variable is Condition , where Variable is an input variable for the antecedent or an output variable for a consequence. The condition portion of the consequence is a fuzzy membership function like “cold,” “warm,” or “hot.” The rule base compares every rule against data from the fuzzifier. When it encounters an antecedent that's true, it triggers the statement's consequence action. This triggering action is referred to as firing .

An example of a rule base for an elevator might look like:

IF door is open AND velocity is zero AND distance is negligible THEN motor is nil
IF door is closed AND velocity is slow AND distance is great THEN motor is maximum
IF door is closed AND velocity is medium AND distance is moderate THEN motor is average
IF door is closed AND velocity is fast AND distance is moderate THEN motor is average

This example has four rules, and each rule has four variables: door, motor, velocity, and distance (to the selected floor). Each rule has three antecedents and one consequence. Together there are 10 conditions that make up the membership sets of the four variables. For instance, velocity has the values zero, slow, medium, and fast for its membership set. The variable distance has values negligible, slight, moderate, and great. Door can be either opened or closed, and motor has nil, minimum, average, and maximum for the respective membership sets.

For this example, let's assume that the elevator's door is closed and its velocity is 10 feet per second, which is defined in both the slow and medium membership regions (all membership regions overlap each other). The distance is 42 feet, which is multiply defined for both the great and moderate regions. Thus, two rules in the rule base fire (the second and third rules), and the consequences are averaged.


Figure 3: Results of fired rule base

Rule firing is a key element in the rule-base component of the controller. When a rule fires it causes the consequence portion of the statement to become active. Once active, the consequence portion of the statement is summed with all the other consequences of fired rules. Thus the output of this stage produces a composite of all fired consequences. Graphically, this looks like the sum of all the membership sets for fired consequences. Figure 3 shows what this may look like for the consequences of the rule base in the elevator example.

The third component of the fuzzy logic controller is the defuzzifier . It converts the output from the previous stage to a crisp output value. Presently four methods are popular for performing defuzzification. They are centroid, maximizer, singleton, and weighted average. Each method has its own advantages and disadvantages, with centroid being the most popular. I'll only describe the centroid method in this article.

Centroid defuzzification computes the “center of mass” for the composite fuzzy sets from the previous stage. This method is numerically intensive and requires computing the integral of the set. It also requires that the output sets overlap to avoid a situation that produces an invalid output, and that each variable's membership set is composed of an odd number or regions.

Code translation
To implement a real-time fuzzy-logic controller on a DSP, you'll need to translate the fuzzy-logic engine design to actual code. To translate each component into software, you must understand the controller, its pieces, and the system it's intended to control.

Translating the main routine to code is straightforward. Essentially it's nothing more than an infinite loop that continually gets data values, converts them to their fuzzified equivalent, processes the fuzzified data, defuzzifies the output to a crisp value, and outputs the value to the appropriate output port. Each of these operations corresponds to a C function and together they make up the fuzzy engine.

Listing 1: Defuzzifier: C code for the main function

main()
{
unsigned char aVal1 = 0,
aVal2 = 0,
GetData(),
loopFlag = true,
*pSPtr,
RuleBase(),
*tempSPtr;
set1
set2
void
pressureSet;
tempSet;
Fuzzifier(),
Defuzzifier(),
ZeroStruct();
pSPtr = (unsigned char *) &pressureSet;       /* set structs to ptrs */
tempSPtr = (unsigned char *) &tempSet;

/* while valid data available, get it */

while ((GetData(&aVal1, &aVal2)) > 0)
{
ZeroStruct((char *) pSPtr, SET1_SIZE);      /* clr structs for every iteration */
ZeroStruct((char *) tempSPtr, SET2_SIZE);

/* take values from file and fuzzify them */

Fuzzifier(aVal1, aVal2, pSPtr, tempSPtr);

/* take fuzzified sets and apply to rule base */

aVal1 = RuleBase((char *) pSPtr, (char *) tempSPtr, SET1_SIZE, SET2_SIZE);

/* defuzzify and output them */

Defuzzifier(aVal1);
      }
}

The GetData() function in Listing 1 retrieves data points from an I/O device. This function is stubbed for testing but when fully implemented it will access the physical I/O device. The function extracts canned data points from an array in this example. It extracts one set of data points per loop iteration and feeds the extracted data to the next function, the fuzzifier.

The Fuzzifier() function translates the data to a fuzzy representation by converting the crisp data values via structures into values that the rule base can use. Fuzzifier() must then clear the data points for each iteration. An extra built-in function clears the structures after every iteration. Once translated, data points are available to the rule base.

RuleBase takes the results of the fuzzifier (the data points) and compares them with the internal rule base table. If a match occurs, RuleBase extracts the corresponding consequence value and uses it for each fired rule. RuleBase then sums all fired rules to produce a final fuzzy result, which it then applies to the Defuzzifier() function.

The Defuzzifier() function translates fuzzy data back into a crisp form that can be used by digital-to-analog converters or other physical devices. Normally, an output section converts this output to a value that the external world can deal with. However, because we have constrained the system by using arrays, the defuzzifier component simply stores the result into an output array. Listings 1 and 2 show the C code for the main function and the GetData() functions.

Listing 2: Defuzzifier: C code for the GetData() function

#define DATA_SIZE 10 /* must be same as data in arrays */

/************** Static Data Vars *****************/

static char dataCount = 0,
    pressData[] = {112, 255, 200, 34, 56, 148, 222, 90, 22, 0,},
    tempData[] = {176, 255, 200, 19, 99, 45, 56, 92, 86, 0};

/**************************************************/

unsigned char GetData(aPtr, bPtr)
    unsigned char *aPtr, *bPtr;

{
*aPtr = pressData[dataCount];       /* get data for press element */
*bPtr = tempData[dataCount];       /* get data for temp element */
dataCount++;

/* chk for terminal condition */

if (dataCount <=>
    return(1); /* return true for not eof */
else
    return(0);
}

To code the fuzzify routine, let's go back to the description of the fuzzifier to make sure we understand how it works. Recall that the fuzzifier converts crisp input data into fuzzy data that can then be operated on. This means that the data is translated into a multi-valued quantity. Furthermore, this quantity is a function of the membership set for that particular input data. A C structure is an ideal data structure to store fuzzified data elements because it can hold multiple values. Each element of the structure corresponds to one of the membership regions. For example, the fuzzy input variable Deflection has three regions: “slight,” “medium,” and “large.” Let's assume that the regions are defined as follows:

    0 < slight=""><>
    10 < medium=""><>
    20 < large="">< 90="">

If a crisp input for this variable has a value of 13, its fuzzified equivalent consists of a multi-valued variable that has three Boolean values: slight, medium, and large. The three elements are set according to the crisp input data. Each field is Boolean and initially set to false. The value 13 falls into two categories, so has elements slight and medium both set to true. Alternatively, an input value of 23 has all three components set to true. Implementing the fuzzifier concept in code consists of creating a C structure, comparing its ranges against the input data, and assigning the appropriate field based on the comparison. Listing 3 shows the implementation of the fuzzifier section. The fuzzifier normally performs scaling, if that is required.

Listing 3 Implementation of fuzzifier section

void Fuzzifier(in1, in2, out1, out2)
    unsigned char in1, in2;
    set1Ptr   out1;
    set2Ptr   out2;

{
/* set all relevent values in the first set structure */

if ((in1 >= SLT_LOW) && (in1 <= slt_high))="" consts="" are="" defined="" for="" the="" range="" of="" ln="">
    out1->slight = true;

if ((in1 >= AV_LOW) && (in1 <=>
    out1->average = true;

if ((in1 >= GRT_LOW) && (in1 <=>
    out1->great = true;

/* set all relevent values in the second set structure */

if ((FC_LOW <= in2)="" &&="" (in2=""><=>
    out2->lo = true;

if ((PO_LOW <= in2)="" &&="" (in2=""><=>
    out2->med = true;

if ((FO_LOW <= in2)="" &&="" (in2=""><=>
    out2->hi = true;
}

The rule base portion of the controller performs the processing of the input data. It's the most complex of the routines since it does most of the work. Remembering that the rule base takes the sum of all fired rule values from the table, and then divides them by the number of fired rules. The table contains the membership region values for each input variable. For instance, the variable Position has three regions and the variable Error has five regions. The table has a total of 15 (five times three) elements. The intersection of a table's row and column corresponds to the AND of two conditions in the antecedent portion of the IF-THEN statement. Values in the matrix increase from left to right, and from top to bottom. For example if Position 's regions are “near,” “close,” and “far,” near corresponds to column zero in the matrix, and close corresponds to column one, and so on. Similarly, if variable Error has “verySmall,” “small,” “medium,” “large,” and “veryLarge” as its members, verySmall corresponds to row zero, small to row two and so on. So the intersection of the row and column in the matrix corresponds to the ANDing of the antecedents. For instance, row zero, column zero corresponds to rule:

IF Position is near AND Error is verySmall THEN . . .

Listing 4: Code for rule base

char RuleBase(memSet1, memSet2, memCount1, memCount2)
   char *memSet1, *memSet2, memCount1, memCount2;

/*******************************************/

{
   unsigned char ave = 0,
      aValue = 0,
      i = 0,
      j = 0,
      ruleFiredCount = 0;
   unsigned int sum = 0;

   /* 3 X 3 matrix. values are modified to reflect changes in rule base */

   static char rules[SET1_SIZE] [SET2_SIZE] =
   {
      { 1, 1, 1 },
      { 2, 2, 2 },
      { 3, 3, 3 },
   };

   /* find first part of antecedent */

   for (i = 0; i < memcount1;="">
   {
      if (*(memSet1 + i) == true)
      {
         for (j = 0; j < memcount2;="">
         {

            if (*(memSet2 + j) == true)
            {
               aValue = rules[i][j];
               ruleFiredCount++;
               sum += aValue;
            }
         }
      }
   }
/* chk 2nd set for truth */

/* get consequence for fired rule */
* inc total num of rules fired */
/* sum consequences */

   if (ruleFiredCount > 0)
      ave = sum / ruleFiredCount;

   return(ave);
}

/* avoid divide-by-zero error */
/* compute ave of singletons */

Listing 4 presents the code for the rule base. Notice that the elements are just ordinal numbers. They're filled later after the model is defined and tuned. Additionally, the table size is a function of the number of rules. Thus, it varies depending on the application. If the table changes, the variables related to this array must also change to reflect its size change.

The defuzzifier module translates the fuzzified output to a crisp output. Normally the defuzzifier converts the fuzzy output to a crisp one and then scales it to the variable's external domain. This engine deviates from traditional defuzzifiers in both respects. First, the actual crisp values are stored in the rule table, so it's not necessary to convert them. In most real-world scenarios, analog input and output usually consists of varying voltages between zero and five volts, varying in time. An example of this is pulse width modulation (PWM), which is commonly used to control motors and servos. Listing 5 presents the code for the defuzzifier. Note that this section also simplifies the external interface by making it accessible via an array. This enables us to focus on debugging the logic. Real I/O is added once the logic is debugged. An additional advantage of using an array for testing is that it makes the processes more portable.

Listing 5: The defuzzifier

void Defuzzifier(aValue)
            unsigned char aValue;

{
      /* storage for output */

      static unsigned char outputArray[10] =
         { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
         theIndex = 0;

      outputArray[theIndex] = aValue; /* store the result in the array */
      theIndex++;
}

Example
Now that the fuzzy logic is complete let's give it a system to control. FuzzyLevel is an example of a DSP-based controller that's used to control the fluid level and pressure in a tank for an industrial control application. This kind of controller is typically used in an oil refinery.


Figure 4: The FuzzyLevel system

FuzzyLevel maintains the tank's fluid at a certain level while making sure the gas pressure within the tank doesn't get dangerously high. The fluid level and pressure of the tank are related. The more fluid in the tank, the higher the pressure. Also, fluid enters the tank at a constant rate. Figure 4 shows a simplified diagram of the FuzzyLevel system. Sensors are employed to monitor gas pressure and fluid height. An actuator is used to control the amount the output valve is opened.

Now that we know how the system behaves, we need to translate that behavior into a design that we can simulate to get its optimal operating point. To make the translation we first need to determine the system's inputs, outputs, and the processing that it does. From the description, we see that the system inputs come from the sensors. Therefore, “pressure” and “height” are input variables. Furthermore, the actuator is the only output in this system. Thus, “valve” is the system's sole output variable. We have yet to describe the system's processing, but we'll come back to that. Next, we need to define ranges for the variables. For this example we'll assume each variable has three regions. For instance, pressure's regions are “low,” “medium,” and “high.” Height's regions are “slight,” “average,” and “great.” While the lone output variable valve's regions are “fullyClosed,” “partiallyOpen,” and “fullyOpen.”

Now that we've defined the variables and their regions it's time to address the system's processing. As mentioned earlier, there's a relationship between the inputs and outputs. Describing this relationship in some notation defines the behavior of our system. Furthermore, this behavior is described by its rules. The number of rules relates to the regions of the input variables. Our system has nine rules since each variable has three regions. We've now completed the rough model.

Next, we need to simulate this model to generate our defuzzified points that go into the fuzzy engine's matrix. For this example, we'll use the TeachFuzz fuzzy-logic simulator from Impatiens Publications. Before translating the design into a format that the simulator can understand, we have to set the actual values of the input and output variables. The simulator doesn't care about the type of units we use; its only requirement is positive numbers. Units are unimportant to the simulator (designers use them for their convenience). Ranges for the variables are as follows: 0 < height="">< 80,="" 0="">< pressure="">< 1,600,="" and="" 0="">< valve="">< 240.="" the="" units="" for="" these="" variables="" are="" in="" feet,="" pounds="" per="" square="" inch,="" and="" foot-pounds="" of="" torque,="" respectively.="" the="" composite="" input="" files="" for="" the="" simulator="" are="" posted="" online="" at="">ftp://ftp.embedded.com/pub/2004/07miller.

Executing the simulator produces observable data about the model. You'll often have to make changes to the model because it doesn't behave as expected. The process of changing the model's parameters is known as tuning . Tuning enables us to find the optimal operating point of the controller. Usually tuning consists of modifying the number of regions and changing the shape of those regions. Our model is no exception. After we complete the tuning we're ready to populate the rule matrix. We can determine the defuzzified values by finding a data set that fires only one rule. That value is put in the matrix at the intersection of the corresponding input variables regions. For example, the following rule is the only one that fired for a given set of input data:

if height is slight, and pressure is med, then valve is partiallyOpen;


Figure 5: Compiling the code

The value of 30 is put in the matrix, which corresponds to the intersection of “height” is “slight” and “pressure” is “med.” This corresponds to the center of gravity of that consequence. This procedure is repeated for all rules until the matrix is fully populated. Figure 5 shows the results of a fully populated table for the FuzzyLevel model. When the matrix is full, the controller is ready for testing.


Figure 6: A screen shot of the downloaded FuzzyLevel application


Figure 7: Code Composer Studio profiling results for the FuzzyLevel application

Profiling the code
Figures 6 and 7 show a compile/ debug session (using TI's Code Composer simulating a 'C5402 DSP chip) after 10 iterations of the code. Inserted printfs are used here to simplify I/O verification. Profile point statistics show that the first iteration takes 722 cycles. Ten iterations take 7,100 cycles, for an average of 710 cycles per iteration. On a 100MHz 'C5402 chip FuzzyLevel takes 0.71 microseconds per iteration from input to output. A response this fast may be overkill for most applications, but is perfectly reasonable for safety-critical applications, such as our fluid tank.

Fast fuzzy
DSP acceleration using fuzzy logic is useful when fast system responses can't be met by using typical microprocessors. Currently, many applications use of this approach, such as some medical anesthesia systems or servo-motor controllers. The Fuzzy Level example is also a simplified control system like some of those currently in use in oil refineries.

You can modify the engine to accommodate more inputs and outputs. Specifically, modify the engine into an N -input by M -output controller by modifying the array. You can add extra inputs by changing the array as a function of the inputs; for example, three inputs—three-dimensional rule matrix. Multiple outputs can be handled in several ways. For instance, the intersection element of the matrix could contain multiple values. Conversely, the intersection element could contain a pointer to a data structure holding all the defuzzified values.

Another enhancement is to make it so that different antecedents trigger only certain consequences. However, this involves a restructuring of the controller, and extensive use of indirection with pointers. If you decide to go this route, make sure that the resulting response time is fast enough to respond to system inputs.

Byron Miller is an independent firmware engineer specializing in the design of microprocessors, DSPs, hardware debug, porting, as well as the development of firmware for control, data acquisition, fuzzy logic, and Internet appliances. He has a BA in Computer Science, and a Masters in Software Engineering degree. E-mail him at .

References

  • Cox, Earl. The Fuzzy Systems Handbook, 2nd Ed. Academic Press, San Diego, CA 1998.
  • Ibrahim, Ahmad. Fuzzy Logic for Embedded Systems Applications. Elsevier Science, Burlington, MA, 2004.
  • Iliev, B., Kalaykov, B., Tero, R. DSP-Based Fast FuzzyLogic Controllers, http://focus.ti.com/pdfs/univ/04-Control.pdf, 2000
  • Jacobs, O. Introduction into Control Theory, 2nd Ed. Oxford University Press Inc., New York, NY., 1993.
  • Kever, Doug, “Fuzzy Logic Smooths PID-Resistive Loop,” Control Engineering , April 1997, p. 71-72.
  • Miller, Byron. The Design and Development of Fuzzy Logic Controllers. Impatiens Publications, Minneapolis, MN., 1997.
  • Miller, Byron. “A Top-Down Approach to Fuzzy Logic Design,” Embedded Systems Programming , July 1998, P. 52.
  • Pedrycz, Witold. Fuzzy Control and Fuzzy Systems . John Wilely & Sons, New York, NY., 1995.
  • Ross, Timothy. Fuzzy Logic with Engineering Applications . McGraw-Hill, New York, NY, 1995.
  • Shieh, J.S., Linkens, D.A., Peacock, J.E., “Hierarchical Rule-Based and Self-Organizing Fuzzy Logic Control for Depth of Anesthesia,” IEEE Trans. Sys. Man Cyb. Part C, SMCC-29, pp. 98-109, 1999.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.