comp.lang.ada
 help / color / mirror / Atom feed
* RFC:  Generic Fixed-Point IIR Filter
@ 2015-03-26 16:25 Patrick Noffke
  2015-03-26 17:05 ` Dmitry A. Kazakov
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Patrick Noffke @ 2015-03-26 16:25 UTC (permalink / raw)


I would greatly appreciate some feedback/help on implementing a generic fixed-point IIR filter package.

I am attempting to define a fixed-point type (Pressure_Type) that has a limited range and precision.  When doing things (like filtering) to values of this type, intermediate calculations require a larger range and possibly higher precision.  But I don't want to expand the range/precision of the base type -- rather define new types as appropriate for the required intermediate calculations.

Below is my first attempt.  I noticed the following issues:
 - I cannot define Filter_Value_Type within the IIR_Filter package (i.e. it has to be a generic parameter).  If I try to put it in the package and base it on Fixed_Type, I get an error that a non-static expression is used for the delta and range.  For example, this gives the error I just mentioned:

   type Filter_Value_Type is delta Fixed_Type'Delta / 10
     range N_A * Fixed_Type'First .. N_A * Fixed_Type'Last;

I realize (from http://en.wikibooks.org/wiki/Ada_Programming/Generics) that generic formal objects are non-static, but it's not clear to me why this won't work for a generic formal type (how can a type be non-static?).  Is there another way to parameterize the delta and range for intermediate calculations?

 - If I try to use Fixed_Type for X_Array and Y_Array, then I do in fact get a constraint error (due to range overflow for an intermediate calculation).  Is there a better way to do a calculation for a fixed-point type when you expect the result to be within the specified range of Fixed_Type, but intermediate calculations may not?

 - I get warnings that the filter constants are not a multiple of small.  I understand why this is (small is the largest (most positive) negative power of two not greater than delta).  I can explicitly modify the constants to be a multiple of small, but is there another way to define the constants that will "convert" them to a multiple of small?

 - I realize my range and precision for Filter_Value_Type are not necessarily ideal (the range depends on the filter coefficients, the filter architecture, as well as the filter order).  Do you know of any references or have suggestions for how to analyze a filter to determine the ideal range and precision?


iir_filter.ads:

generic
   --  The fixed-point numeric type for the filter input and output.
   type Fixed_Type is delta <>;
   --  The fixed-point numeric type for intermediate filter calculations.
   --  Should have enough range to avoid overflow and enough precision so
   --  rounding errors are insignificant.
   type Filter_Value_Type is delta <>;
   --  Number of "a" coefficients.
   N_A : Positive;
   --  Number of "b" coefficients.
   N_B : Positive;
package IIR_Filter is
   type A_Coefficient_Array_Type is array (1 .. N_A) of Filter_Value_Type;
   type B_Coefficient_Array_Type is array (1 .. N_B) of Filter_Value_Type;
   type IIR_Filter_Type is private;

   procedure Init_Filter (Filter         : in out IIR_Filter_Type;
                          A_Coefficients :        A_Coefficient_Array_Type;
                          B_Coefficients :        B_Coefficient_Array_Type;
                          Initial_Value  :        Fixed_Type);

   function Step_Filter (Filter : in out IIR_Filter_Type;
                         X      :        Fixed_Type) return Fixed_Type;

private
   type Y_Array_Type is array (1 .. N_A) of Filter_Value_Type;
   type X_Array_Type is array (1 .. N_B) of Filter_Value_Type;
   type IIR_Filter_Type is record
      A : A_Coefficient_Array_Type;
      B : B_Coefficient_Array_Type;
      X_Array : X_Array_Type;
      Y_Array : Y_Array_Type;
   end record;

end IIR_Filter;

iif_filter.adb:

package body IIR_Filter is

   procedure Init_Filter (Filter         : in out IIR_Filter_Type;
                          A_Coefficients :        A_Coefficient_Array_Type;
                          B_Coefficients :        B_Coefficient_Array_Type;
                          Initial_Value  :        Fixed_Type) is
   begin
      Filter.A := A_Coefficients;
      Filter.B := B_Coefficients;
      for X_Index in Filter.X_Array'Range loop
         Filter.X_Array (X_Index) := Filter_Value_Type (Initial_Value);
      end loop;
      for Y_Index in Filter.Y_Array'Range loop
         Filter.Y_Array (Y_Index) := Filter_Value_Type (Initial_Value);
      end loop;
   end Init_Filter;

   function Step_Filter (Filter : in out IIR_Filter_Type;
                         X      :        Fixed_Type) return Fixed_Type is
   begin
      --  Compute IIR filter using Direct Form I.

      --  Move the elements to next oldest position.
      --  TODO:  Use a circular buffer for this.
      if Filter.X_Array'Length > 1 then
         for X_Index in reverse 2 .. Filter.X_Array'Last loop
            Filter.X_Array (X_Index) := Filter.X_Array (X_Index - 1);
         end loop;
      end if;

      if Filter.Y_Array'Length > 1 then
         for Y_Index in reverse 2 .. Filter.Y_Array'Last loop
            Filter.Y_Array (Y_Index) := Filter.Y_Array (Y_Index - 1);
         end loop;
      end if;

      -- Store the new X value.
      Filter.X_Array (1) := Filter_Value_Type (X);

      --  Compute new filter output.  Initialize with b(1) * x(n).
      Filter.Y_Array (1) := Filter.B (1) * X;

      --  Compute b(2) * x(n - 1) + b(3) * x(n - 2) + ...
      if Filter.X_Array'Length > 1 then
         for X_Index in 2 .. Filter.X_Array'Last loop
            Filter.Y_Array (1) := Filter.Y_Array (1) +
              Filter.B (X_Index) * Filter.X_Array (X_Index);
         end loop;
      end if;

      --  Compute -a(2) * y(n - 1) - a(3) * y(n - 2) + ...
      if Filter.Y_Array'Length > 1 then
         for Y_Index in 2 .. Filter.Y_Array'Last loop
            Filter.Y_Array (1) := Filter.Y_Array (1) -
              Filter.A (Y_Index) * Filter.Y_Array (Y_Index);
         end loop;
      end if;

      --  Divide by a(1).
      Filter.Y_Array (1) := Filter.Y_Array (1) / Filter.A (1);

      return Fixed_Type (Filter.Y_Array (1));
   end Step_Filter;

end IIR_Filter;

pressure.ads:

package Pressure is
   type Pressure_Type is delta 0.001 range -20.0 .. 400.0;
end Pressure;


Then in my main procedure:

procedure Filter_Test is
   N_A : constant Positive := 3;
   N_B : constant Positive := 3;
   type Filter_Value_Type is delta Pressure.Pressure_Type'Delta / 10
     range N_A * Pressure.Pressure_Type'First .. N_A * Pressure.Pressure_Type'Last;
   package Pressure_IIR_Filter is new
     IIR_Filter (Fixed_Type => Pressure.Pressure_Type, Filter_Value_Type => Filter_Value_Type,
                 N_A => N_A, N_B => N_B);
   Pressure_Filter : Pressure_IIR_Filter.IIR_Filter_Type;
   A_Coeffs : constant Pressure_IIR_Filter.A_Coefficient_Array_Type :=
     (1.0, -1.84036, 0.85219);
   B_Coeffs : constant Pressure_IIR_Filter.B_Coefficient_Array_Type :=
     (0.0029583, 0.0059165, 0.0029583);
   Filtered_Pressure : Pressure.Pressure_Type := 0.0;

begin

   Pressure_IIR_Filter.Init_Filter (Pressure_Filter, A_Coeffs, B_Coeffs, 0.0);

   for Step_Index in 1 .. 200 loop
      Filtered_Pressure :=
        Pressure_IIR_Filter.Step_Filter (Pressure_Filter, 300.0);
      Put_Line ("Step: " & Step_Index'Img & ", Filtered_Pressure = " &
                  Filtered_Pressure'Img);
   end loop;

end Filter_Test;


Regards,
Patrick

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2015-03-27 12:17 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-26 16:25 RFC: Generic Fixed-Point IIR Filter Patrick Noffke
2015-03-26 17:05 ` Dmitry A. Kazakov
2015-03-26 17:41 ` Jeffrey Carter
2015-03-26 19:44   ` Patrick Noffke
2015-03-26 20:34     ` Randy Brukardt
2015-03-26 20:39       ` Jeffrey Carter
2015-03-26 21:12         ` Randy Brukardt
2015-03-26 21:30           ` Patrick Noffke
2015-03-26 22:02           ` Jeffrey Carter
2015-03-27 12:17           ` G.B.
2015-03-26 21:17       ` Patrick Noffke
2015-03-26 20:37     ` Jeffrey Carter
2015-03-27 11:37 ` Brian Drummond

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox