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 }