Fixed point types and sine wave generation

| |

We've found that the variety of data types offered by the compiler is
of paramount importance to the ease and effectiveness of embedded
programming. Having lots of data types makes implementing your
programming solution easier. It also removes a big source of error:
the hand-optimization needed to encode data in assembly programs.

Starting in 2007 we're offering a large variety of fixed point data
types in new releases of our compilers. Fixed point data types handle
fractional values ("Fractional" types) and occasionally types with
fractional and small integer components ("Accumulator" types).

These are the relevant types:

Type Description
fixed168 16 bits integer, 8 bits fraction
fixed1616 16 bits integer, 16 bits fraction
_Fract 16 bits fraction
fract8 8 bits fraction
fract16 16 bits fraction
fract24 24 bits fraction
_Accum 8 bits integer, 16 bits fraction
accum88 8 bits integer, 8 bits fraction
accum816 8 bits integer, 16 bits fraction
accum824 8 bits integer, 24 bits fraction

If you're tempted to use floating-point math in your application,
consider fixed-point instead. The processing is substantially easier
and less memory-intensive, and the underlying math is easier to
verify.

When your programming challenge deals with fractional values, you can
describe them naturally and combine them with integers with ease. You
don't have to remember to perform conversions or use translated number
systems: the compiler performs data promotion automatically.

Catch the wave

One good use of fractional values is in sine wave generation. It helps
to keep the sine wave values as fractional when dealing with a
variable magnitude described as an integer.

There are two ways to generate accurate sine waves: using the
transcendental function and using "rectangle rule integration".

Sine wave using fixed point transcendentals

Byte Craft Limited offers a fixed point transcendental library
suitable for use with our fixed point data types. It is retargetable:
you can change the underlying data type (just redefine "real" and the
real fixed-point suffix) to the smallest necessary resolution and
recompile.

This is a program that emits a PWM sinewave:

#pragma option f 0;
#include <9RS08KA2.h>

/* This is a sine wave function generator for RS08.
   It will generate a PWM function on an output pin
   suitable to pass through an RC network.
   
   It makes use of the two timers on the RS08: 
   the slower 32kHz RTI graphs the sine wave, 
   and the faster MTIM switches the output. 
   
   This software uses threads to manage the 
   event-driven work of generating the signal.

*/ 

//define real accum88
//define matching _rs real suffix to match real,
//for use with constants
#define real accum88
#define _rs hk 

#include >FIX_math.h<


#define OUTPUT (PORTA.0)


/* PWM uses the modulo overflow to change from on duty to off duty. */

_Bool on_duty;
unsigned int8 duty;

#pragma thread PWM() INTERRUPT (MTIMSC.TOF);

void PWM(void)
{
  if(on_duty)
    {
      OUTPUT = 1;
      MTIMMOD = duty; //reset TOF    
    }
  else
    {
      OUTPUT = 0;
      MTIMMOD = 255 - duty; //reset TOF
    }

  on_duty = ~on_duty;  
}

#pragma thread sine_wave() INTERRUPT (SRTISC.RTIF);

#define TWOPI (__fix_pi * 2)
//put a 1 in the LS fract bit of an accum88
#define MINFRACT ((real)0x0001)

unsigned real position;

void sine_wave(void)
{
  //update the duty cycle
  duty = 128 + (int8)(127.0 /* _rs */ 
                            * fix_sin(position));
  //advance to next position in waveform  
  position = (position + MINFRACT);
  //loop
  if (position > TWOPI) position = 0;
  SRTISC.RTIACK = 1; 
}


void main(void)
{
  on_duty = 0;
  duty = 128;
  
  position = 0.0r;
 
  //start the RTI
  SRTISC.CLKS = 0;     //1 kHz oscillator
  SRTISC.RTIS = 0b001; //8ms period 
  SRTISC.RTIACK = 1;   //reset any stray interrupt
  SRTISC.RTIE = 1;     //enable
    
  //start the PWM
  MTIMCLK.CLKS = 0b01; //fixed-frequency clock
  MTIMCLK.PS = 0;      //no prescaling
  MTIMMOD = duty;      //initial reset of TOF
  MTIMSC.TOIE = 1;     //interrupt enable
  MTIMSC.TSTP = 0;     //enable
   
  while(1)
    __DISPATCH();

}

#include <FIX_math.c>

Sine wave using rectangle-rule integration

This is a different method of generating both sine and cosine
waves. It generates less code but does not use radian measurements or
keep its results within the range -1..1.

#pragma option f 0;
#include <9RS08KA2.h>

/* This is a sine wave function generator for RS08.
   It will generate a PWM function on an output pin
   suitable to pass through an RC network.
   
   It makes use of the two timers on the RS08: 
   the slower 32kHz RTI graphs the sine wave, 
   and the faster MTIM switches the output. 
   
   This software uses threads to manage the 
   event-driven work of generating the signal.

*/ 

//prototype
void __DISPATCH(void);

//define real accum88
//define matching _rs real suffix to match real,
//for use with constants
#define real accum88
#define _rs hk 




#define OUTPUT (PORTA.0)



/* PWM uses the modulo overflow to change from on duty to off duty. */

_Bool on_duty;
unsigned int8 duty;

#pragma thread PWM() INTERRUPT (MTIMSC.TOF);

void PWM(void)
{
  if(on_duty)
    {
      OUTPUT = 1;
      MTIMMOD = duty; //reset TOF 
    }
  else
    {
      OUTPUT = 0;
      MTIMMOD = 255 - duty; //reset TOF
    }

  on_duty = ~on_duty;  
}

#pragma thread sine_wave() INTERRUPT (SRTISC.RTIF);

//accumulators for sine and cosine waveforms.
real sv, cv;
//temporary
real temp;

//8ms time interval
#define t_int (8/1000)

void sine_wave(void)
{
  //store sine value before alteration.
  temp = sv;
  sv = cv * t_int;
  cv = temp * t_int; //sv

  //use the sine value
  duty = 128 + (int8)(sv);

  SRTISC.RTIACK = 1; 
}


void main(void)
{
  on_duty = 0;
  duty = 128;

  //initialize
  sv = 127;
  cv = 0;
  
  //start the RTI
  SRTISC.CLKS = 0;     //1 kHz oscillator
  SRTISC.RTIS = 0b001; //8ms period 
  SRTISC.RTIACK = 1;   //reset any stray interrupt
  SRTISC.RTIE = 1;     //enable
    
  //start the PWM
  MTIMCLK.CLKS = 0b01; //fixed-frequency clock
  MTIMCLK.PS = 0;      //no prescaling
  MTIMMOD = duty;      //initial reset of TOF
  MTIMSC.TOIE = 1;     //interrupt enable
  MTIMSC.TSTP = 0;     //enable
   
  while(1)
    __DISPATCH();

}