/ src / hal / components / pwmgen.c
pwmgen.c
  1  /********************************************************************
  2  * Description:  pwmgen.c
  3  *               A HAL component that generates Pulse Width and
  4  *               Pulse Density Modulation
  5  *
  6  * Author: John Kasunich
  7  * License: GPL Version 2
  8  *    
  9  * Copyright (c) 2006 All rights reserved.
 10  *
 11  * Last change: 
 12  ********************************************************************/
 13  /** This file, 'pwmgen.c', is a HAL component that generates
 14      Pulse Width Modulation or Pulse Density Modulation signals in
 15      software.  Since the timing granularity of a software based
 16      scheme is rather large compared to a hardware, either the
 17      output frequency, or the resolution, or both, will be less
 18      than expected from hardware implementations.
 19  
 20      The driver exports two functions.  'pwmgen.make-pulses', is
 21      responsible for actually generating the PWM/PDM signals.  It
 22      must be executed in a fast thread to reduce pulse jitter and
 23      improve resolution.  The other function, pwmgen.update, is
 24      normally called from a much slower thread.  It reads the 
 25      command and sets internal variables used by 'pwm.make-pulses'.
 26      'update' uses floating point, 'make-pulses' does not.
 27  
 28  
 29      Polarity:
 30  
 31      All signals from this module have fixed polarity (active high)
 32      If the driver needs the opposite polarity, the signals can be
 33      inverted using parameters exported by the hardware driver(s) 
 34      such as ParPort.
 35  
 36  
 37  */
 38  
 39  /** Copyright (C) 2003 John Kasunich
 40                         <jmkasunich AT users DOT sourceforge DOT net>
 41  */
 42  
 43  /** This program is free software; you can redistribute it and/or
 44      modify it under the terms of version 2 of the GNU General
 45      Public License as published by the Free Software Foundation.
 46      This library is distributed in the hope that it will be useful,
 47      but WITHOUT ANY WARRANTY; without even the implied warranty of
 48      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 49      GNU General Public License for more details.
 50  
 51      You should have received a copy of the GNU General Public
 52      License along with this library; if not, write to the Free Software
 53      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 54  
 55      THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR
 56      ANY HARM OR LOSS RESULTING FROM ITS USE.  IT IS _EXTREMELY_ UNWISE
 57      TO RELY ON SOFTWARE ALONE FOR SAFETY.  Any machinery capable of
 58      harming persons must have provisions for completely removing power
 59      from all motors, etc, before persons enter any danger area.  All
 60      machinery must be designed to comply with local and national safety
 61      codes, and the authors of this software can not, and do not, take
 62      any responsibility for such compliance.
 63  
 64      This code was written as part of the EMC HAL project.  For more
 65      information, go to www.linuxcnc.org.
 66  */
 67  
 68  #include "rtapi.h"		/* RTAPI realtime OS API */
 69  #include "rtapi_app.h"		/* RTAPI realtime module decls */
 70  #include "hal.h"		/* HAL public API decls */
 71  
 72  #define MAX_CHAN 8
 73  
 74  /* module information */
 75  MODULE_AUTHOR("John Kasunich");
 76  MODULE_DESCRIPTION("PWM/PDM Generator for EMC HAL");
 77  MODULE_LICENSE("GPL");
 78  #define MAX_OUTPUT_TYPE 2
 79  int output_type[MAX_CHAN] = { -1, -1, -1, -1, -1, -1, -1, -1 };
 80  RTAPI_MP_ARRAY_INT(output_type, 8, "output types for up to 8 channels");
 81  
 82  /***********************************************************************
 83  *                STRUCTURES AND GLOBAL VARIABLES                       *
 84  ************************************************************************/
 85  
 86  /* values for pwm_mode */
 87  #define PWM_DISABLED 0
 88  #define PWM_PURE 1
 89  #define PWM_DITHER 2
 90  #define PWM_PDM 3
 91  
 92  
 93  typedef struct {
 94      long period;		/* length of PWM period, ns */
 95      long high_time;		/* desired high time, ns */
 96      long period_timer;		/* timer for PWM period */
 97      long high_timer;		/* timer for high time */
 98      unsigned char curr_output;	/* current state of output */
 99      unsigned char output_type;
100      unsigned char pwm_mode;
101      unsigned char direction;
102      hal_bit_t *out[2];		/* pins for output signals */
103  
104      hal_bit_t *enable;		/* pin for enable signal */
105      hal_float_t *value;		/* command value */
106      hal_float_t *scale;		/* pin: scaling from value to duty cycle */
107      hal_float_t *offset;	/* pin: offset: this is added to duty cycle */
108      double old_scale;		/* stored scale value */
109      double scale_recip;		/* reciprocal value used for scaling */
110      hal_float_t *pwm_freq;	/* pin: (max) output frequency in Hz */
111      double old_pwm_freq;	/* used to detect changes */
112      int periods;		/* number of periods in PWM cycle */
113      double periods_recip;	/* reciprocal */
114      hal_bit_t *dither_pwm;	/* 0 = pure PWM, 1 = dithered PWM */
115      hal_float_t *min_dc;	/* pin: minimum duty cycle */
116      hal_float_t *max_dc;	/* pin: maximum duty cycle */
117      hal_float_t *curr_dc;	/* pin: current duty cycle */
118  } pwmgen_t;
119  
120  /* ptr to array of pwmgen_t structs in shared memory, 1 per channel */
121  static pwmgen_t *pwmgen_array;
122  
123  #define PWM_PIN		0	/* output phase used for PWM signal */
124  #define DIR_PIN		1	/* output phase used for DIR signal */
125  #define UP_PIN		0	/* output phase used for UP signal */
126  #define DOWN_PIN	1	/* output phase used for DOWN signal */
127  
128  /* other globals */
129  static int comp_id;		/* component ID */
130  static int num_chan;		/* number of pwm generators configured */
131  static long periodns;		/* makepulses function period in nanosec */
132  
133  /***********************************************************************
134  *                  LOCAL FUNCTION DECLARATIONS                         *
135  ************************************************************************/
136  
137  static int export_pwmgen(int num, pwmgen_t * addr, int output_type);
138  static void make_pulses(void *arg, long period);
139  static void update(void *arg, long period);
140  
141  /***********************************************************************
142  *                       INIT AND EXIT CODE                             *
143  ************************************************************************/
144  
145  int rtapi_app_main(void)
146  {
147      int n, retval;
148  
149      for (n = 0; n < MAX_CHAN && output_type[n] != -1 ; n++) {
150  	if ((output_type[n] > MAX_OUTPUT_TYPE) || (output_type[n] < 0)) {
151  	    rtapi_print_msg(RTAPI_MSG_ERR,
152  		"PWMGEN: ERROR: bad output type '%i', channel %i\n",
153  		output_type[n], n);
154  	    return -1;
155  	} else {
156  	    num_chan++;
157  	}
158      }
159      if (num_chan == 0) {
160  	rtapi_print_msg(RTAPI_MSG_ERR,
161  	    "PWMGEN: ERROR: no channels configured\n");
162  	return -1;
163      }
164      /* periodns will be set to the proper value when 'make_pulses()' runs for
165         the first time.  We load a default value here to avoid glitches at
166         startup */
167      periodns = -1;
168      /* have good config info, connect to the HAL */
169      comp_id = hal_init("pwmgen");
170      if (comp_id < 0) {
171  	rtapi_print_msg(RTAPI_MSG_ERR, "PWMGEN: ERROR: hal_init() failed\n");
172  	return -1;
173      }
174      /* allocate shared memory for generator data */
175      pwmgen_array = hal_malloc(num_chan * sizeof(pwmgen_t));
176      if (pwmgen_array == 0) {
177  	rtapi_print_msg(RTAPI_MSG_ERR,
178  	    "PWMGEN: ERROR: hal_malloc() failed\n");
179  	hal_exit(comp_id);
180  	return -1;
181      }
182      /* export all the variables for each PWM generator */
183      for (n = 0; n < num_chan; n++) {
184  	/* export all vars */
185  	retval = export_pwmgen(n, &(pwmgen_array[n]), output_type[n]);
186  	if (retval != 0) {
187  	    rtapi_print_msg(RTAPI_MSG_ERR,
188  		"PWMGEN: ERROR: pwmgen %d var export failed\n", n);
189  	    hal_exit(comp_id);
190  	    return -1;
191  	}
192      }
193      /* export functions */
194      retval = hal_export_funct("pwmgen.make-pulses", make_pulses,
195  	pwmgen_array, 0, 0, comp_id);
196      if (retval != 0) {
197  	rtapi_print_msg(RTAPI_MSG_ERR,
198  	    "PWMGEN: ERROR: makepulses funct export failed\n");
199  	hal_exit(comp_id);
200  	return -1;
201      }
202      retval = hal_export_funct("pwmgen.update", update,
203  	pwmgen_array, 1, 0, comp_id);
204      if (retval != 0) {
205  	rtapi_print_msg(RTAPI_MSG_ERR,
206  	    "PWMGEN: ERROR: update funct export failed\n");
207  	hal_exit(comp_id);
208  	return -1;
209      }
210      rtapi_print_msg(RTAPI_MSG_INFO,
211  	"PWMGEN: installed %d PWM/PDM generators\n", num_chan);
212      hal_ready(comp_id);
213      return 0;
214  }
215  
216  void rtapi_app_exit(void)
217  {
218      hal_exit(comp_id);
219  }
220  
221  /***********************************************************************
222  *              REALTIME STEP PULSE GENERATION FUNCTIONS                *
223  ************************************************************************/
224  
225  /** The pwm generator works by adding a positive value proportional
226      to the desired duty cycle to accumulator.  When the accumulator
227      is greater than zero, the output goes high (if permitted by the
228      maximum frequency).  When the output is high, a larger value is
229      subtracted from the accumulator.  As a result, it oscillates 
230      around zero to generate PWM or PDM, based on the values that are
231      added and subtracted.
232  */
233  
234  static void make_pulses(void *arg, long period)
235  {
236      pwmgen_t *pwmgen;
237      int n;
238  
239      /* store period for use in update() function */
240      periodns = period;
241      /* point to pwmgen data structures */
242      pwmgen = arg;
243      for (n = 0; n < num_chan; n++) {
244  
245  	switch ( pwmgen->pwm_mode ) {
246  
247  	case PWM_PURE:
248  	    if ( pwmgen->curr_output ) {
249  		/* current state is high, update cumlative high time */
250  		pwmgen->high_timer += periodns;
251  		/* have we been high long enough? */
252  		if ( pwmgen->high_timer >= pwmgen->high_time ) {
253  		    /* yes, terminate the high time */
254  		    pwmgen->curr_output = 0;
255  		}
256  	    }
257  	    /* update period timer */
258  	    pwmgen->period_timer += periodns;
259  	    /* have we reached the end of a period? */
260  	    if ( pwmgen->period_timer >= pwmgen->period ) {
261  		/* reset both timers to zero for jitter-free output */
262  		pwmgen->period_timer = 0;
263  		pwmgen->high_timer = 0;
264  		/* start the next period */
265  		if ( pwmgen->high_time > 0 ) {
266  		    pwmgen->curr_output = 1;
267  		}
268  	    }
269  	    break;
270  	case PWM_DITHER:
271  	    if ( pwmgen->curr_output ) {
272  		/* current state is high, update cumlative high time */
273  		pwmgen->high_timer -= periodns;
274  		/* have we been high long enough? */
275  		if ( pwmgen->high_timer <= 0 ) {
276  		    /* yes, terminate the high time */
277  		    pwmgen->curr_output = 0;
278  		}
279  	    }
280  	    /* update period timer */
281  	    pwmgen->period_timer += periodns;
282  	    /* have we reached the end of a period? */
283  	    if ( pwmgen->period_timer >= pwmgen->period ) {
284  		/* update both timers, retain remainder from last period */
285  		/* this allows dithering for finer resolution */
286  		pwmgen->period_timer -= pwmgen->period;
287  		pwmgen->high_timer += pwmgen->high_time;
288  		/* start the next period */
289  		if ( pwmgen->high_timer > 0 ) {
290  		    pwmgen->curr_output = 1;
291  		}
292  	    }
293  	    break;
294  	case PWM_PDM:
295  	    /* add desired high time to running total */
296  	    pwmgen->high_timer += pwmgen->high_time;
297  	    if ( pwmgen->curr_output ) {
298  		/* current state is high, subtract actual high time */
299  		pwmgen->high_timer -= periodns;
300  	    }
301  	    if ( pwmgen->high_timer > 0 ) {
302  		pwmgen->curr_output = 1;
303  	    } else {
304  		pwmgen->curr_output = 0;
305  	    }
306  	    break;
307  	case PWM_DISABLED:
308  	default:
309  	    /* disabled, drive output off and zero accumulator */
310  	    pwmgen->curr_output = 0;
311  	    pwmgen->high_timer = 0;
312  	    pwmgen->period_timer = 0;
313  	    break;
314  	}
315  	/* generate output, based on output type */
316  	if (pwmgen->output_type < 2) {
317  	    /* PWM (and maybe DIR) output */
318  	    /* DIR is set by update(), we only do PWM */
319  	    *(pwmgen->out[PWM_PIN]) = pwmgen->curr_output;
320  	} else {
321  	    /* UP and DOWN output */
322  	    *(pwmgen->out[UP_PIN]) = pwmgen->curr_output & ~pwmgen->direction;
323  	    *(pwmgen->out[DOWN_PIN]) = pwmgen->curr_output & pwmgen->direction;
324  	}
325  	/* move on to next PWM generator */
326  	pwmgen++;
327      }
328      /* done */
329  }
330  
331  static void update(void *arg, long period)
332  {
333  	static long oldperiodns=-1;
334  
335      pwmgen_t *pwmgen;
336      int n, high_periods;
337      unsigned char new_pwm_mode;
338      double tmpdc, outdc;
339  
340      /* update the PWM generators */
341      pwmgen = arg;
342      for (n = 0; n < num_chan; n++) {
343  
344  	/* validate duty cycle limits, both limits must be between
345  	   0.0 and 1.0 (inclusive) and max must be greater then min */
346  	if ( *(pwmgen->max_dc) > 1.0 ) {
347  	    *(pwmgen->max_dc) = 1.0;
348  	}
349  	if ( *(pwmgen->min_dc) > *(pwmgen->max_dc) ) {
350  	    *(pwmgen->min_dc) = *(pwmgen->max_dc);
351  	}
352  	if ( *(pwmgen->min_dc) < 0.0 ) {
353  	    *(pwmgen->min_dc) = 0.0;
354  	}
355  	if ( *(pwmgen->max_dc) < *(pwmgen->min_dc) ) {
356  	    *(pwmgen->max_dc) = *(pwmgen->min_dc);
357  	}
358  	/* do scale calcs only when scale changes */
359  	if ( *(pwmgen->scale) != pwmgen->old_scale ) {
360  	    /* get ready to detect future scale changes */
361  	    pwmgen->old_scale = *(pwmgen->scale);
362  	    /* validate the new scale value */
363  	    if ((*(pwmgen->scale) < 1e-20)
364  		&& (*(pwmgen->scale) > -1e-20)) {
365  		/* value too small, divide by zero is a bad thing */
366  		*(pwmgen->scale) = 1.0;
367  	    }
368  	    /* we will need the reciprocal */
369  	    pwmgen->scale_recip = 1.0 / *(pwmgen->scale);
370  	}
371  	if ( *(pwmgen->enable) == 0 ) {
372  	    new_pwm_mode = PWM_DISABLED;
373  	} else if ( *(pwmgen->pwm_freq) == 0 ) {
374  	    new_pwm_mode = PWM_PDM;
375  	} else if ( *(pwmgen->dither_pwm) != 0 ) {
376  	    new_pwm_mode = PWM_DITHER;
377  	} else {
378  	    new_pwm_mode = PWM_PURE;
379  	}
380  	/* force recalc if max_freq is changed */
381  	if ( *(pwmgen->pwm_freq) != pwmgen->old_pwm_freq ) {
382  	    pwmgen->pwm_mode = PWM_DISABLED;
383  	}
384  	/* do the period calcs when mode, pwm_freq, or periodns changes */
385  	if ( ( pwmgen->pwm_mode != new_pwm_mode )
386  		|| ( periodns != oldperiodns ) ) {
387  	    /* disable output during calcs */
388  	    pwmgen->pwm_mode = PWM_DISABLED;
389  	    /* validate max_freq */
390  	    if ( *(pwmgen->pwm_freq) <= 0.0 ) {
391  		/* zero or negative means PDM mode */
392  		*(pwmgen->pwm_freq) = 0.0;
393  		pwmgen->period = periodns;
394  	    } else {
395  		/* positive means PWM mode */
396  		if ( *(pwmgen->pwm_freq) < 0.5 ) {
397   		    /* min freq is 0.5 Hz (2 billion nsec period) */
398  		    *(pwmgen->pwm_freq) = 0.5;
399  		} else if ( *(pwmgen->pwm_freq) > ((1e9/2.0) / periodns) ) {
400  		    /* max freq is 2 base periods */
401  		    *(pwmgen->pwm_freq) = (1e9/2.0) / periodns;
402  		}
403  		if ( new_pwm_mode == PWM_PURE ) {
404  		    /* period must be integral multiple of periodns */
405  		    pwmgen->periods = (( 1e9 / *(pwmgen->pwm_freq) ) / periodns ) + 0.5;
406  		    pwmgen->periods_recip = 1.0 / pwmgen->periods;
407  		    pwmgen->period = pwmgen->periods * periodns;
408  		    /* actual max freq after rounding */
409  		    *(pwmgen->pwm_freq) = 1.0e9 / pwmgen->period;
410  		} else {
411  		    pwmgen->period = 1.0e9 / *(pwmgen->pwm_freq);
412  		}
413  	    }
414  	    /* save freq to detect changes */
415  	    pwmgen->old_pwm_freq = *(pwmgen->pwm_freq);
416  	}
417  	/* convert value command to duty cycle */
418  	tmpdc = *(pwmgen->value) * pwmgen->scale_recip + *(pwmgen->offset);
419  	if ( pwmgen->output_type == 0 ) {
420  	    /* unidirectional mode, no negative output */
421  	    if ( tmpdc < 0.0 ) {
422  		tmpdc = 0.0;
423  	    }
424  	}
425  	/* limit the duty cycle */
426  	if (tmpdc >= 0.0) {
427  	    if ( tmpdc > *(pwmgen->max_dc) ) {
428  		tmpdc = *(pwmgen->max_dc);
429  	    } else if ( tmpdc < *(pwmgen->min_dc) ) {
430  		tmpdc = *(pwmgen->min_dc);
431  	    }
432  	    pwmgen->direction = 0;
433  	    outdc = tmpdc;
434  	} else {
435  	    if ( tmpdc < -*(pwmgen->max_dc) ) {
436  		tmpdc = -*(pwmgen->max_dc);
437  	    } else if ( tmpdc > -*(pwmgen->min_dc) ) {
438  		tmpdc = -*(pwmgen->min_dc);
439  	    }
440  	    pwmgen->direction = 1;
441  	    outdc = -tmpdc;
442  	}
443  	if ( new_pwm_mode == PWM_PURE ) {
444  	    /* round to nearest pure PWM duty cycle */
445  	    high_periods = (pwmgen->periods * outdc) + 0.5;
446  	    pwmgen->high_time = high_periods * periodns;
447  	    /* save rounded value to curr_dc pin */
448  	    if ( tmpdc >= 0 ) {
449  		*(pwmgen->curr_dc) = high_periods * pwmgen->periods_recip;
450  	    } else {
451  		*(pwmgen->curr_dc) = -high_periods * pwmgen->periods_recip;
452  	    }
453  	} else {
454  	    pwmgen->high_time = ( pwmgen->period * outdc ) + 0.5;
455  	    /* save duty cycle to curr_dc pin */
456  	    *(pwmgen->curr_dc) = tmpdc;
457  	}
458  	/* if using PWM/DIR outputs, set DIR pin */
459  	if ( pwmgen->output_type == 1 ) {
460  	    *(pwmgen->out[DIR_PIN]) = pwmgen->direction;
461  	}
462  	/* save new mode */
463  	pwmgen->pwm_mode = new_pwm_mode;
464  	/* move on to next channel */
465  	pwmgen++;
466      }
467      oldperiodns = periodns;
468      /* done */
469  }
470  
471  
472  /***********************************************************************
473  *                   LOCAL FUNCTION DEFINITIONS                         *
474  ************************************************************************/
475  
476  static int export_pwmgen(int num, pwmgen_t * addr, int output_type)
477  {
478      int retval, msg;
479  
480      /* This function exports a lot of stuff, which results in a lot of
481         logging if msg_level is at INFO or ALL. So we save the current value
482         of msg_level and restore it later.  If you actually need to log this
483         function's actions, change the second line below */
484      msg = rtapi_get_msg_level();
485      rtapi_set_msg_level(RTAPI_MSG_WARN);
486  
487      /* export pins */
488      retval = hal_pin_float_newf(HAL_IO, &(addr->scale), comp_id,
489  	    "pwmgen.%d.scale", num);
490      if (retval != 0) {
491  	return retval;
492      }
493      retval = hal_pin_float_newf(HAL_IO, &(addr->offset), comp_id,
494  	    "pwmgen.%d.offset", num);
495      if (retval != 0) {
496  	return retval;
497      }
498      retval = hal_pin_bit_newf(HAL_IO, &(addr->dither_pwm), comp_id,
499  	    "pwmgen.%d.dither-pwm", num);
500      if (retval != 0) {
501  	return retval;
502      }
503      retval = hal_pin_float_newf(HAL_IO, &(addr->pwm_freq), comp_id,
504  	    "pwmgen.%d.pwm-freq", num);
505      if (retval != 0) {
506  	return retval;
507      }
508      retval = hal_pin_float_newf(HAL_IO, &(addr->min_dc), comp_id,
509  	    "pwmgen.%d.min-dc", num);
510      if (retval != 0) {
511  	return retval;
512      }
513      retval = hal_pin_float_newf(HAL_IO, &(addr->max_dc), comp_id,
514  	    "pwmgen.%d.max-dc", num);
515      if (retval != 0) {
516  	return retval;
517      }
518      retval = hal_pin_float_newf(HAL_OUT, &(addr->curr_dc), comp_id,
519  	    "pwmgen.%d.curr-dc", num);
520      if (retval != 0) {
521  	return retval;
522      }
523      retval = hal_pin_bit_newf(HAL_IN, &(addr->enable), comp_id,
524  	    "pwmgen.%d.enable", num);
525      if (retval != 0) {
526  	return retval;
527      }
528      *(addr->enable) = 0;
529      retval = hal_pin_float_newf(HAL_IN, &(addr->value), comp_id,
530  	    "pwmgen.%d.value", num);
531      if (retval != 0) {
532  	return retval;
533      }
534      *(addr->value) = 0.0;
535      if (output_type == 2) {
536  	/* export UP/DOWN pins */
537  	retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[UP_PIN]), comp_id,
538  		"pwmgen.%d.up", num);
539  	if (retval != 0) {
540  	    return retval;
541  	}
542  	/* init the pin */
543  	*(addr->out[UP_PIN]) = 0;
544  	retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[DOWN_PIN]), comp_id,
545  		"pwmgen.%d.down", num);
546  	if (retval != 0) {
547  	    return retval;
548  	}
549  	/* init the pin */
550  	*(addr->out[DOWN_PIN]) = 0;
551      } else {
552  	/* export PWM pin */
553  	retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[PWM_PIN]), comp_id,
554  		"pwmgen.%d.pwm", num);
555  	if (retval != 0) {
556  	    return retval;
557  	}
558  	/* init the pin */
559  	*(addr->out[PWM_PIN]) = 0;
560  	if ( output_type == 1 ) {
561  	    /* export DIR pin */
562  	    retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[DIR_PIN]), comp_id,
563  		    "pwmgen.%d.dir", num);
564  	    if (retval != 0) {
565  		return retval;
566  	    }
567  	    /* init the pin */
568  	    *(addr->out[DIR_PIN]) = 0;
569  	}
570      }
571      /* set default pin values */
572      *(addr->scale) = 1.0;
573      *(addr->offset) = 0.0;
574      *(addr->dither_pwm) = 0;
575      *(addr->pwm_freq) = 0;
576      *(addr->min_dc) = 0.0;
577      *(addr->max_dc) = 1.0;
578      *(addr->curr_dc) = 0.0;
579      /* init other fields */
580      addr->period = 50000;
581      addr->high_time = 0;
582      addr->period_timer = 0;
583      addr->high_timer = 0;
584      addr->curr_output = 0;
585      addr->output_type = output_type;
586      addr->pwm_mode = PWM_DISABLED;
587      addr->direction = 0;
588      addr->old_scale = *(addr->scale) + 1.0;
589      addr->old_pwm_freq = -1;
590      /* restore saved message level */
591      rtapi_set_msg_level(msg);
592      return 0;
593  }