summaryrefslogtreecommitdiff
path: root/src/hal/components/pwmgen.c
blob: b43c588307be27d6b2dd668cfe2657138049a670 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
/********************************************************************
* Description:  pwmgen.c
*               A HAL component that generates Pulse Width and
*               Pulse Density Modulation
*
* Author: John Kasunich
* License: GPL Version 2
*    
* Copyright (c) 2006 All rights reserved.
*
* Last change: 
********************************************************************/
/** This file, 'pwmgen.c', is a HAL component that generates
    Pulse Width Modulation or Pulse Density Modulation signals in
    software.  Since the timing granularity of a software based
    scheme is rather large compared to a hardware, either the
    output frequency, or the resolution, or both, will be less
    than expected from hardware implementations.

    The driver exports two functions.  'pwmgen.make-pulses', is
    responsible for actually generating the PWM/PDM signals.  It
    must be executed in a fast thread to reduce pulse jitter and
    improve resolution.  The other function, pwmgen.update, is
    normally called from a much slower thread.  It reads the 
    command and sets internal variables used by 'pwm.make-pulses'.
    'update' uses floating point, 'make-pulses' does not.


    Polarity:

    All signals from this module have fixed polarity (active high)
    If the driver needs the opposite polarity, the signals can be
    inverted using parameters exported by the hardware driver(s) 
    such as ParPort.


*/

/** Copyright (C) 2003 John Kasunich
                       <jmkasunich AT users DOT sourceforge DOT net>
*/

/** This program is free software; you can redistribute it and/or
    modify it under the terms of version 2 of the GNU General
    Public License as published by the Free Software Foundation.
    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111 USA

    THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR
    ANY HARM OR LOSS RESULTING FROM ITS USE.  IT IS _EXTREMELY_ UNWISE
    TO RELY ON SOFTWARE ALONE FOR SAFETY.  Any machinery capable of
    harming persons must have provisions for completely removing power
    from all motors, etc, before persons enter any danger area.  All
    machinery must be designed to comply with local and national safety
    codes, and the authors of this software can not, and do not, take
    any responsibility for such compliance.

    This code was written as part of the EMC HAL project.  For more
    information, go to www.linuxcnc.org.
*/

#include "rtapi.h"		/* RTAPI realtime OS API */
#include "rtapi_app.h"		/* RTAPI realtime module decls */
#include "hal.h"		/* HAL public API decls */

#define MAX_CHAN 8

/* module information */
MODULE_AUTHOR("John Kasunich");
MODULE_DESCRIPTION("PWM/PDM Generator for EMC HAL");
MODULE_LICENSE("GPL");
#define MAX_OUTPUT_TYPE 2
int output_type[MAX_CHAN] = { -1, -1, -1, -1, -1, -1, -1, -1 };
RTAPI_MP_ARRAY_INT(output_type, 8, "output types for up to 8 channels");

/***********************************************************************
*                STRUCTURES AND GLOBAL VARIABLES                       *
************************************************************************/

/* values for pwm_mode */
#define PWM_DISABLED 0
#define PWM_PURE 1
#define PWM_DITHER 2
#define PWM_PDM 3


typedef struct {
    long period;		/* length of PWM period, ns */
    long high_time;		/* desired high time, ns */
    long period_timer;		/* timer for PWM period */
    long high_timer;		/* timer for high time */
    unsigned char curr_output;	/* current state of output */
    unsigned char output_type;
    unsigned char pwm_mode;
    unsigned char direction;
    hal_bit_t *out[2];		/* pins for output signals */

    hal_bit_t *enable;		/* pin for enable signal */
    hal_float_t *value;		/* command value */
    hal_float_t *scale;		/* pin: scaling from value to duty cycle */
    hal_float_t *offset;	/* pin: offset: this is added to duty cycle */
    double old_scale;		/* stored scale value */
    double scale_recip;		/* reciprocal value used for scaling */
    hal_float_t *pwm_freq;	/* pin: (max) output frequency in Hz */
    double old_pwm_freq;	/* used to detect changes */
    int periods;		/* number of periods in PWM cycle */
    double periods_recip;	/* reciprocal */
    hal_bit_t *dither_pwm;	/* 0 = pure PWM, 1 = dithered PWM */
    hal_float_t *min_dc;	/* pin: minimum duty cycle */
    hal_float_t *max_dc;	/* pin: maximum duty cycle */
    hal_float_t *curr_dc;	/* pin: current duty cycle */
} pwmgen_t;

/* ptr to array of pwmgen_t structs in shared memory, 1 per channel */
static pwmgen_t *pwmgen_array;

#define PWM_PIN		0	/* output phase used for PWM signal */
#define DIR_PIN		1	/* output phase used for DIR signal */
#define UP_PIN		0	/* output phase used for UP signal */
#define DOWN_PIN	1	/* output phase used for DOWN signal */

/* other globals */
static int comp_id;		/* component ID */
static int num_chan;		/* number of pwm generators configured */
static long periodns;		/* makepulses function period in nanosec */

/***********************************************************************
*                  LOCAL FUNCTION DECLARATIONS                         *
************************************************************************/

static int export_pwmgen(int num, pwmgen_t * addr, int output_type);
static void make_pulses(void *arg, long period);
static void update(void *arg, long period);

/***********************************************************************
*                       INIT AND EXIT CODE                             *
************************************************************************/

int rtapi_app_main(void)
{
    int n, retval;

    for (n = 0; n < MAX_CHAN && output_type[n] != -1 ; n++) {
	if ((output_type[n] > MAX_OUTPUT_TYPE) || (output_type[n] < 0)) {
	    rtapi_print_msg(RTAPI_MSG_ERR,
		"PWMGEN: ERROR: bad output type '%i', channel %i\n",
		output_type[n], n);
	    return -1;
	} else {
	    num_chan++;
	}
    }
    if (num_chan == 0) {
	rtapi_print_msg(RTAPI_MSG_ERR,
	    "PWMGEN: ERROR: no channels configured\n");
	return -1;
    }
    /* periodns will be set to the proper value when 'make_pulses()' runs for
       the first time.  We load a default value here to avoid glitches at
       startup */
    periodns = -1;
    /* have good config info, connect to the HAL */
    comp_id = hal_init("pwmgen");
    if (comp_id < 0) {
	rtapi_print_msg(RTAPI_MSG_ERR, "PWMGEN: ERROR: hal_init() failed\n");
	return -1;
    }
    /* allocate shared memory for generator data */
    pwmgen_array = hal_malloc(num_chan * sizeof(pwmgen_t));
    if (pwmgen_array == 0) {
	rtapi_print_msg(RTAPI_MSG_ERR,
	    "PWMGEN: ERROR: hal_malloc() failed\n");
	hal_exit(comp_id);
	return -1;
    }
    /* export all the variables for each PWM generator */
    for (n = 0; n < num_chan; n++) {
	/* export all vars */
	retval = export_pwmgen(n, &(pwmgen_array[n]), output_type[n]);
	if (retval != 0) {
	    rtapi_print_msg(RTAPI_MSG_ERR,
		"PWMGEN: ERROR: pwmgen %d var export failed\n", n);
	    hal_exit(comp_id);
	    return -1;
	}
    }
    /* export functions */
    retval = hal_export_funct("pwmgen.make-pulses", make_pulses,
	pwmgen_array, 0, 0, comp_id);
    if (retval != 0) {
	rtapi_print_msg(RTAPI_MSG_ERR,
	    "PWMGEN: ERROR: makepulses funct export failed\n");
	hal_exit(comp_id);
	return -1;
    }
    retval = hal_export_funct("pwmgen.update", update,
	pwmgen_array, 1, 0, comp_id);
    if (retval != 0) {
	rtapi_print_msg(RTAPI_MSG_ERR,
	    "PWMGEN: ERROR: update funct export failed\n");
	hal_exit(comp_id);
	return -1;
    }
    rtapi_print_msg(RTAPI_MSG_INFO,
	"PWMGEN: installed %d PWM/PDM generators\n", num_chan);
    hal_ready(comp_id);
    return 0;
}

void rtapi_app_exit(void)
{
    hal_exit(comp_id);
}

/***********************************************************************
*              REALTIME STEP PULSE GENERATION FUNCTIONS                *
************************************************************************/

/** The pwm generator works by adding a positive value proportional
    to the desired duty cycle to accumulator.  When the accumulator
    is greater than zero, the output goes high (if permitted by the
    maximum frequency).  When the output is high, a larger value is
    subtracted from the accumulator.  As a result, it oscillates 
    around zero to generate PWM or PDM, based on the values that are
    added and subtracted.
*/

static void make_pulses(void *arg, long period)
{
    pwmgen_t *pwmgen;
    int n;

    /* store period for use in update() function */
    periodns = period;
    /* point to pwmgen data structures */
    pwmgen = arg;
    for (n = 0; n < num_chan; n++) {

	switch ( pwmgen->pwm_mode ) {

	case PWM_PURE:
	    if ( pwmgen->curr_output ) {
		/* current state is high, update cumlative high time */
		pwmgen->high_timer += periodns;
		/* have we been high long enough? */
		if ( pwmgen->high_timer >= pwmgen->high_time ) {
		    /* yes, terminate the high time */
		    pwmgen->curr_output = 0;
		}
	    }
	    /* update period timer */
	    pwmgen->period_timer += periodns;
	    /* have we reached the end of a period? */
	    if ( pwmgen->period_timer >= pwmgen->period ) {
		/* reset both timers to zero for jitter-free output */
		pwmgen->period_timer = 0;
		pwmgen->high_timer = 0;
		/* start the next period */
		if ( pwmgen->high_time > 0 ) {
		    pwmgen->curr_output = 1;
		}
	    }
	    break;
	case PWM_DITHER:
	    if ( pwmgen->curr_output ) {
		/* current state is high, update cumlative high time */
		pwmgen->high_timer -= periodns;
		/* have we been high long enough? */
		if ( pwmgen->high_timer <= 0 ) {
		    /* yes, terminate the high time */
		    pwmgen->curr_output = 0;
		}
	    }
	    /* update period timer */
	    pwmgen->period_timer += periodns;
	    /* have we reached the end of a period? */
	    if ( pwmgen->period_timer >= pwmgen->period ) {
		/* update both timers, retain remainder from last period */
		/* this allows dithering for finer resolution */
		pwmgen->period_timer -= pwmgen->period;
		pwmgen->high_timer += pwmgen->high_time;
		/* start the next period */
		if ( pwmgen->high_timer > 0 ) {
		    pwmgen->curr_output = 1;
		}
	    }
	    break;
	case PWM_PDM:
	    /* add desired high time to running total */
	    pwmgen->high_timer += pwmgen->high_time;
	    if ( pwmgen->curr_output ) {
		/* current state is high, subtract actual high time */
		pwmgen->high_timer -= periodns;
	    }
	    if ( pwmgen->high_timer > 0 ) {
		pwmgen->curr_output = 1;
	    } else {
		pwmgen->curr_output = 0;
	    }
	    break;
	case PWM_DISABLED:
	default:
	    /* disabled, drive output off and zero accumulator */
	    pwmgen->curr_output = 0;
	    pwmgen->high_timer = 0;
	    pwmgen->period_timer = 0;
	    break;
	}
	/* generate output, based on output type */
	if (pwmgen->output_type < 2) {
	    /* PWM (and maybe DIR) output */
	    /* DIR is set by update(), we only do PWM */
	    *(pwmgen->out[PWM_PIN]) = pwmgen->curr_output;
	} else {
	    /* UP and DOWN output */
	    *(pwmgen->out[UP_PIN]) = pwmgen->curr_output & ~pwmgen->direction;
	    *(pwmgen->out[DOWN_PIN]) = pwmgen->curr_output & pwmgen->direction;
	}
	/* move on to next PWM generator */
	pwmgen++;
    }
    /* done */
}

static void update(void *arg, long period)
{
	static long oldperiodns=-1;

    pwmgen_t *pwmgen;
    int n, high_periods;
    unsigned char new_pwm_mode;
    double tmpdc, outdc;

    /* update the PWM generators */
    pwmgen = arg;
    for (n = 0; n < num_chan; n++) {

	/* validate duty cycle limits, both limits must be between
	   0.0 and 1.0 (inclusive) and max must be greater then min */
	if ( *(pwmgen->max_dc) > 1.0 ) {
	    *(pwmgen->max_dc) = 1.0;
	}
	if ( *(pwmgen->min_dc) > *(pwmgen->max_dc) ) {
	    *(pwmgen->min_dc) = *(pwmgen->max_dc);
	}
	if ( *(pwmgen->min_dc) < 0.0 ) {
	    *(pwmgen->min_dc) = 0.0;
	}
	if ( *(pwmgen->max_dc) < *(pwmgen->min_dc) ) {
	    *(pwmgen->max_dc) = *(pwmgen->min_dc);
	}
	/* do scale calcs only when scale changes */
	if ( *(pwmgen->scale) != pwmgen->old_scale ) {
	    /* get ready to detect future scale changes */
	    pwmgen->old_scale = *(pwmgen->scale);
	    /* validate the new scale value */
	    if ((*(pwmgen->scale) < 1e-20)
		&& (*(pwmgen->scale) > -1e-20)) {
		/* value too small, divide by zero is a bad thing */
		*(pwmgen->scale) = 1.0;
	    }
	    /* we will need the reciprocal */
	    pwmgen->scale_recip = 1.0 / *(pwmgen->scale);
	}
	if ( *(pwmgen->enable) == 0 ) {
	    new_pwm_mode = PWM_DISABLED;
	} else if ( *(pwmgen->pwm_freq) == 0 ) {
	    new_pwm_mode = PWM_PDM;
	} else if ( *(pwmgen->dither_pwm) != 0 ) {
	    new_pwm_mode = PWM_DITHER;
	} else {
	    new_pwm_mode = PWM_PURE;
	}
	/* force recalc if max_freq is changed */
	if ( *(pwmgen->pwm_freq) != pwmgen->old_pwm_freq ) {
	    pwmgen->pwm_mode = PWM_DISABLED;
	}
	/* do the period calcs when mode, pwm_freq, or periodns changes */
	if ( ( pwmgen->pwm_mode != new_pwm_mode )
		|| ( periodns != oldperiodns ) ) {
	    /* disable output during calcs */
	    pwmgen->pwm_mode = PWM_DISABLED;
	    /* validate max_freq */
	    if ( *(pwmgen->pwm_freq) <= 0.0 ) {
		/* zero or negative means PDM mode */
		*(pwmgen->pwm_freq) = 0.0;
		pwmgen->period = periodns;
	    } else {
		/* positive means PWM mode */
		if ( *(pwmgen->pwm_freq) < 0.5 ) {
 		    /* min freq is 0.5 Hz (2 billion nsec period) */
		    *(pwmgen->pwm_freq) = 0.5;
		} else if ( *(pwmgen->pwm_freq) > ((1e9/2.0) / periodns) ) {
		    /* max freq is 2 base periods */
		    *(pwmgen->pwm_freq) = (1e9/2.0) / periodns;
		}
		if ( new_pwm_mode == PWM_PURE ) {
		    /* period must be integral multiple of periodns */
		    pwmgen->periods = (( 1e9 / *(pwmgen->pwm_freq) ) / periodns ) + 0.5;
		    pwmgen->periods_recip = 1.0 / pwmgen->periods;
		    pwmgen->period = pwmgen->periods * periodns;
		    /* actual max freq after rounding */
		    *(pwmgen->pwm_freq) = 1.0e9 / pwmgen->period;
		} else {
		    pwmgen->period = 1.0e9 / *(pwmgen->pwm_freq);
		}
	    }
	    /* save freq to detect changes */
	    pwmgen->old_pwm_freq = *(pwmgen->pwm_freq);
	}
	/* convert value command to duty cycle */
	tmpdc = *(pwmgen->value) * pwmgen->scale_recip + *(pwmgen->offset);
	if ( pwmgen->output_type == 0 ) {
	    /* unidirectional mode, no negative output */
	    if ( tmpdc < 0.0 ) {
		tmpdc = 0.0;
	    }
	}
	/* limit the duty cycle */
	if (tmpdc >= 0.0) {
	    if ( tmpdc > *(pwmgen->max_dc) ) {
		tmpdc = *(pwmgen->max_dc);
	    } else if ( tmpdc < *(pwmgen->min_dc) ) {
		tmpdc = *(pwmgen->min_dc);
	    }
	    pwmgen->direction = 0;
	    outdc = tmpdc;
	} else {
	    if ( tmpdc < -*(pwmgen->max_dc) ) {
		tmpdc = -*(pwmgen->max_dc);
	    } else if ( tmpdc > -*(pwmgen->min_dc) ) {
		tmpdc = -*(pwmgen->min_dc);
	    }
	    pwmgen->direction = 1;
	    outdc = -tmpdc;
	}
	if ( new_pwm_mode == PWM_PURE ) {
	    /* round to nearest pure PWM duty cycle */
	    high_periods = (pwmgen->periods * outdc) + 0.5;
	    pwmgen->high_time = high_periods * periodns;
	    /* save rounded value to curr_dc param */
	    if ( tmpdc >= 0 ) {
		*(pwmgen->curr_dc) = high_periods * pwmgen->periods_recip;
	    } else {
		*(pwmgen->curr_dc) = -high_periods * pwmgen->periods_recip;
	    }
	} else {
	    pwmgen->high_time = ( pwmgen->period * outdc ) + 0.5;
	    /* save duty cycle to curr_dc param */
	    *(pwmgen->curr_dc) = tmpdc;
	}
	/* if using PWM/DIR outputs, set DIR pin */
	if ( pwmgen->output_type == 1 ) {
	    *(pwmgen->out[DIR_PIN]) = pwmgen->direction;
	}
	/* save new mode */
	pwmgen->pwm_mode = new_pwm_mode;
	/* move on to next channel */
	pwmgen++;
    }
    oldperiodns = periodns;
    /* done */
}


/***********************************************************************
*                   LOCAL FUNCTION DEFINITIONS                         *
************************************************************************/

static int export_pwmgen(int num, pwmgen_t * addr, int output_type)
{
    int retval, msg;

    /* This function exports a lot of stuff, which results in a lot of
       logging if msg_level is at INFO or ALL. So we save the current value
       of msg_level and restore it later.  If you actually need to log this
       function's actions, change the second line below */
    msg = rtapi_get_msg_level();
    rtapi_set_msg_level(RTAPI_MSG_WARN);

    /* export paramameters */
    retval = hal_pin_float_newf(HAL_IO, &(addr->scale), comp_id,
	    "pwmgen.%d.scale", num);
    if (retval != 0) {
	return retval;
    }
    retval = hal_pin_float_newf(HAL_IO, &(addr->offset), comp_id,
	    "pwmgen.%d.offset", num);
    if (retval != 0) {
	return retval;
    }
    retval = hal_pin_bit_newf(HAL_IO, &(addr->dither_pwm), comp_id,
	    "pwmgen.%d.dither-pwm", num);
    if (retval != 0) {
	return retval;
    }
    retval = hal_pin_float_newf(HAL_IO, &(addr->pwm_freq), comp_id,
	    "pwmgen.%d.pwm-freq", num);
    if (retval != 0) {
	return retval;
    }
    retval = hal_pin_float_newf(HAL_IO, &(addr->min_dc), comp_id,
	    "pwmgen.%d.min-dc", num);
    if (retval != 0) {
	return retval;
    }
    retval = hal_pin_float_newf(HAL_IO, &(addr->max_dc), comp_id,
	    "pwmgen.%d.max-dc", num);
    if (retval != 0) {
	return retval;
    }
    retval = hal_pin_float_newf(HAL_OUT, &(addr->curr_dc), comp_id,
	    "pwmgen.%d.curr-dc", num);
    if (retval != 0) {
	return retval;
    }
    /* export pins */
    retval = hal_pin_bit_newf(HAL_IN, &(addr->enable), comp_id,
	    "pwmgen.%d.enable", num);
    if (retval != 0) {
	return retval;
    }
    *(addr->enable) = 0;
    retval = hal_pin_float_newf(HAL_IN, &(addr->value), comp_id,
	    "pwmgen.%d.value", num);
    if (retval != 0) {
	return retval;
    }
    *(addr->value) = 0.0;
    if (output_type == 2) {
	/* export UP/DOWN pins */
	retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[UP_PIN]), comp_id,
		"pwmgen.%d.up", num);
	if (retval != 0) {
	    return retval;
	}
	/* init the pin */
	*(addr->out[UP_PIN]) = 0;
	retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[DOWN_PIN]), comp_id,
		"pwmgen.%d.down", num);
	if (retval != 0) {
	    return retval;
	}
	/* init the pin */
	*(addr->out[DOWN_PIN]) = 0;
    } else {
	/* export PWM pin */
	retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[PWM_PIN]), comp_id,
		"pwmgen.%d.pwm", num);
	if (retval != 0) {
	    return retval;
	}
	/* init the pin */
	*(addr->out[PWM_PIN]) = 0;
	if ( output_type == 1 ) {
	    /* export DIR pin */
	    retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[DIR_PIN]), comp_id,
		    "pwmgen.%d.dir", num);
	    if (retval != 0) {
		return retval;
	    }
	    /* init the pin */
	    *(addr->out[DIR_PIN]) = 0;
	}
    }
    /* set default pin values */
    *(addr->scale) = 1.0;
    *(addr->offset) = 0.0;
    *(addr->dither_pwm) = 0;
    *(addr->pwm_freq) = 0;
    *(addr->min_dc) = 0.0;
    *(addr->max_dc) = 1.0;
    *(addr->curr_dc) = 0.0;
    /* init other fields */
    addr->period = 50000;
    addr->high_time = 0;
    addr->period_timer = 0;
    addr->high_timer = 0;
    addr->curr_output = 0;
    addr->output_type = output_type;
    addr->pwm_mode = PWM_DISABLED;
    addr->direction = 0;
    addr->old_scale = *(addr->scale) + 1.0;
    addr->old_pwm_freq = -1;
    /* restore saved message level */
    rtapi_set_msg_level(msg);
    return 0;
}