Skip to content

Commit 8f0e384

Browse files
committed
Add dither option to PWMGen for improved analog resolution
1 parent e51d14e commit 8f0e384

File tree

3 files changed

+74
-15
lines changed

3 files changed

+74
-15
lines changed

docs/man/man9/hostmot2.9

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,12 @@ a -1 value results in a 0% duty cycle and +1 results in a 100% duty cycle (with
685685
This mode is used by some PWM motor drives and PWM to analog converters.
686686
Typically the direction signal is not used in this mode.
687687

688+
.TP
689+
(bit input) dither
690+
When True, dither causes the PWM output to dither between two adjacent PWM register values at the PWM frequency.
691+
This increases the PWM resolution when used for analog output purposes, increasing the maximum resolution
692+
from 12 to 16 bits. Dither is only supported with PWMGen firmware version 1 or greater and only affects PWM outputs,
693+
not PDM outputs.
688694

689695
In addition to the per-instance HAL Parameters listed above, there are a couple of HAL Parameters that affect all the pwmgen instances:
690696

src/hal/drivers/mesa-hostmot2/hostmot2.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ typedef struct {
513513
hal_float_t scale;
514514
hal_bit_t offset_mode;
515515
hal_s32_t output_type;
516+
hal_bit_t dither;
516517
} param;
517518

518519
} hal;
@@ -528,6 +529,11 @@ typedef struct {
528529
// this keeps track of the enable bit for this instance that we've told
529530
// the FPGA, so we know if we need to update it
530531
rtapi_s32 written_enable;
532+
533+
// this keeps track of the dither bit for this instance that we've told
534+
// the FPGA, so we know if we need to update it
535+
rtapi_s32 written_dither;
536+
531537
} hm2_pwmgen_instance_t;
532538

533539

@@ -558,7 +564,7 @@ typedef struct {
558564

559565
// number of bits of resolution of the PWM signal (PDM is fixed at 12 bits)
560566
int pwm_bits;
561-
567+
int firmware_supports_dither;
562568

563569
rtapi_u32 pwm_value_addr;
564570
rtapi_u32 *pwm_value_reg;
@@ -576,6 +582,7 @@ typedef struct {
576582
rtapi_u32 enable_reg; // one register for the whole Function
577583
} hm2_pwmgen_t;
578584

585+
579586
//
580587
// oneshot
581588
//

src/hal/drivers/mesa-hostmot2/pwmgen.c

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,11 @@ void hm2_pwmgen_force_write(hostmot2_t *hm2) {
265265
}
266266

267267
hm2->pwmgen.pwm_mode_reg[i] |= (double_buffered << 5);
268+
if (hm2->pwmgen.instance[i].hal.param.dither) {
269+
hm2->pwmgen.pwm_mode_reg[i] |= (1 << 6);
270+
}
271+
272+
268273
}
269274

270275

@@ -287,6 +292,7 @@ void hm2_pwmgen_force_write(hostmot2_t *hm2) {
287292
for (i = 0; i < hm2->pwmgen.num_instances; i ++) {
288293
hm2->pwmgen.instance[i].written_output_type = hm2->pwmgen.instance[i].hal.param.output_type;
289294
hm2->pwmgen.instance[i].written_offset_mode = hm2->pwmgen.instance[i].hal.param.offset_mode;
295+
hm2->pwmgen.instance[i].written_dither = hm2->pwmgen.instance[i].hal.param.dither;
290296
hm2->pwmgen.instance[i].written_enable = *hm2->pwmgen.instance[i].hal.pin.enable;
291297
}
292298

@@ -309,7 +315,7 @@ void hm2_pwmgen_write(hostmot2_t *hm2) {
309315
// check output type
310316
for (i = 0; i < hm2->pwmgen.num_instances; i ++) {
311317
if (hm2->pwmgen.instance[i].hal.param.output_type != hm2->pwmgen.instance[i].written_output_type) {
312-
goto force_write;
318+
goto force_write;
313319
}
314320
}
315321
// check offset mode
@@ -318,6 +324,13 @@ void hm2_pwmgen_write(hostmot2_t *hm2) {
318324
goto force_write;
319325
}
320326
}
327+
328+
// update dither?
329+
for (i = 0; i < hm2->pwmgen.num_instances; i ++) {
330+
if (hm2->pwmgen.instance[i].hal.param.dither != hm2->pwmgen.instance[i].written_dither) {
331+
goto force_write;
332+
}
333+
321334
// check pwm & pdm frequency
322335
if (hm2->pwmgen.hal->param.pwm_frequency != hm2->pwmgen.written_pwm_frequency) goto force_write;
323336
if (hm2->pwmgen.hal->param.pdm_frequency != hm2->pwmgen.written_pdm_frequency) goto force_write;
@@ -329,6 +342,8 @@ void hm2_pwmgen_write(hostmot2_t *hm2) {
329342
}
330343
}
331344

345+
}
346+
332347
return;
333348

334349
force_write:
@@ -346,12 +361,17 @@ int hm2_pwmgen_parse_md(hostmot2_t *hm2, int md_index) {
346361
//
347362
// some standard sanity checks
348363
//
349-
350-
if (!hm2_md_is_consistent_or_complain(hm2, md_index, 0, 5, 4, 0x0003)) {
351-
HM2_ERR("inconsistent Module Descriptor!\n");
352-
return -EINVAL;
353-
}
354-
364+
hm2->pwmgen.firmware_supports_dither = 0;
365+
if (hm2_md_is_consistent(hm2, md_index, 0, 5, 4, 0x0003)) {
366+
// OK, standard old firmware
367+
} else if (hm2_md_is_consistent(hm2, md_index, 1, 5, 4, 0x0003)) {
368+
// OK, firmware with dither capability
369+
hm2->pwmgen.firmware_supports_dither = 1;
370+
} else {
371+
HM2_ERR("Unsupported PWM firmware version");
372+
return -EINVAL;
373+
}
374+
355375
if (hm2->pwmgen.num_instances != 0) {
356376
HM2_ERR(
357377
"found duplicate Module Descriptor for %s (inconsistent firmware), not loading driver\n",
@@ -477,15 +497,22 @@ int hm2_pwmgen_parse_md(hostmot2_t *hm2, int md_index) {
477497
HM2_ERR("error adding pin '%s', aborting\n", name);
478498
goto fail1;
479499
}
480-
500+
481501
// parameters
482502
rtapi_snprintf(name, sizeof(name), "%s.pwmgen.%02d.offset-mode", hm2->llio->name, i);
483503
r = hal_param_bit_new(name, HAL_RW, &(hm2->pwmgen.instance[i].hal.param.offset_mode), hm2->llio->comp_id);
484504
if (r < 0) {
485505
HM2_ERR("error adding param '%s', aborting\n", name);
486506
goto fail1;
487507
}
488-
508+
if (hm2->pwmgen.firmware_supports_dither) {
509+
rtapi_snprintf(name, sizeof(name), "%s.pwmgen.%02d.dither", hm2->llio->name, i);
510+
r = hal_param_bit_new(name, HAL_RW, &(hm2->pwmgen.instance[i].hal.param.dither), hm2->llio->comp_id);
511+
if (r < 0) {
512+
HM2_ERR("error adding param '%s', aborting\n", name);
513+
goto fail1;
514+
}
515+
}
489516
rtapi_snprintf(name, sizeof(name), "%s.pwmgen.%02d.scale", hm2->llio->name, i);
490517
r = hal_param_float_new(name, HAL_RW, &(hm2->pwmgen.instance[i].hal.param.scale), hm2->llio->comp_id);
491518
if (r < 0) {
@@ -509,11 +536,13 @@ int hm2_pwmgen_parse_md(hostmot2_t *hm2, int md_index) {
509536
// init hal objects
510537
*(hm2->pwmgen.instance[i].hal.pin.enable) = 0;
511538
*(hm2->pwmgen.instance[i].hal.pin.value) = 0.0;
512-
hm2->pwmgen.instance[i].hal.param.scale = 1.0;
539+
hm2->pwmgen.instance[i].hal.param.dither = 0;
540+
hm2->pwmgen.instance[i].hal.param.scale = 1.0;
513541
hm2->pwmgen.instance[i].hal.param.offset_mode = 0;
514542
hm2->pwmgen.instance[i].hal.param.output_type = HM2_PWMGEN_OUTPUT_TYPE_PWM;
515543
hm2->pwmgen.instance[i].written_output_type = -666; // force an update at the start
516544
hm2->pwmgen.instance[i].written_enable = -666; // force an update at the start
545+
hm2->pwmgen.instance[i].written_dither = -666; // force an update at the start
517546
}
518547
}
519548

@@ -576,12 +605,13 @@ void hm2_pwmgen_print_module(hostmot2_t *hm2) {
576605

577606
void hm2_pwmgen_prepare_tram_write(hostmot2_t *hm2) {
578607
int i;
579-
608+
double topdrop;
580609
if (hm2->pwmgen.num_instances <= 0) return;
581610

582611
for (i = 0; i < hm2->pwmgen.num_instances; i ++) {
583612
double scaled_value;
584613
double abs_duty_cycle;
614+
double register_value;
585615
int bits;
586616

587617
scaled_value = *hm2->pwmgen.instance[i].hal.pin.value / hm2->pwmgen.instance[i].hal.param.scale;
@@ -609,7 +639,15 @@ void hm2_pwmgen_prepare_tram_write(hostmot2_t *hm2) {
609639
} else {
610640
bits = hm2->pwmgen.pwm_bits;
611641
}
612-
hm2->pwmgen.pwm_value_reg[i] = abs_duty_cycle * (double)((1 << bits) - 1);
642+
// With normal PWM, the max PWM register value is 0xNFF.X
643+
// but for dithered PWM the max value is 0xNFE.F
644+
// the topdrop value is chosen to generate these max values
645+
if (hm2->pwmgen.instance[i].hal.param.dither == 0) {
646+
topdrop = 1;
647+
} else {
648+
topdrop = 1.0625;
649+
}
650+
register_value = abs_duty_cycle * (double)((1 << bits) - topdrop);
613651

614652
} else {
615653
// offset PWM/PDM modes where 0 PWM value = 50% duty cycle also choose active low
@@ -618,9 +656,17 @@ void hm2_pwmgen_prepare_tram_write(hostmot2_t *hm2) {
618656
} else {
619657
bits = hm2->pwmgen.pwm_bits -1;
620658
}
621-
hm2->pwmgen.pwm_value_reg[i] = scaled_value * (double)(((1 << bits) - 1))+ (1 << bits);
659+
// With normal PWM, the max PWM register value is 0xNFF.X
660+
// but for dithered PWM the max value is 0xNFE.F
661+
// the topdrop value is chosen to generate these max values
662+
if (hm2->pwmgen.instance[i].hal.param.dither == 0) {
663+
topdrop = 1;
664+
} else {
665+
topdrop = 1.0625;
666+
}
667+
register_value = scaled_value * (double)(((1 << bits) - topdrop))+ (1 << bits);
622668
}
623-
hm2->pwmgen.pwm_value_reg[i] <<= 16;
669+
hm2->pwmgen.pwm_value_reg[i] = register_value * 65536;
624670
if (scaled_value < 0) {
625671
hm2->pwmgen.pwm_value_reg[i] |= (1 << 31);
626672
}

0 commit comments

Comments
 (0)