TS-7840 FPGA PWM

From embeddedTS Manuals

The TS-7840 includes a PWM core that supports 10-bit duty/period, a 50 MHz input clock, and 12 values of input clock shift.

Linux supports this API through the /sys/ interface using file I/O. First export the pwm channel to enable it:

# Export PWM channel 0
echo 0 >/sys/class/pwm/pwmchip60/export
File Description
/sys/class/pwm/pwmchip60/pwm0/period Period in nanoseconds. Must be bigger than the duty cycle or writes will fail. Can only change when the pwm is disabled.
/sys/class/pwm/pwmchip60/pwm0/duty_cycle Duty cycle in nanoseconds. Can change at any time, must be less than period.
/sys/class/pwm/pwmchip60/pwm0/enable When 1, pwm is outputting. When 0, outputs idle state of the PWM.
/sys/class/pwm/pwmchip60/pwm0/polarity When "normal", idle high and duty cycle low. When "inversed", idle low and duty cycle high.

For example, for a 50hz signal with 25% duty cycle:

# Set Period to 20ms
echo 20000000 > /sys/class/pwm/pwmchip60/pwm0/period
# Set duty cycle to 5ms
echo 5000000 > /sys/class/pwm/pwmchip60/pwm0/duty_cycle
# Enable PWM and output 50hz signal
echo 1 > /sys/class/pwm/pwmchip60/pwm0/enable

# Duty cycle can be changed while it is enabled
echo 1000000 > /sys/class/pwm/pwmchip60/pwm0/duty_cycle

The Linux PWM API will attempt to arrive at the exact period at the cost of the duty cycle resolution. For the most possible duty cycle resolution use one of the max period ns values from the table below.

Shift PWM Input Frequency (hz) Max Period (ns) Max Period (hz)
0 50000000 20460 48876
1 25000000 40920 24438
2 12500000 81840 12219
3 6250000 163680 6109
4 3125000 327360 3055
5 1562500 654720 1527
6 781250 1309440 1210
7 390625 2618880 382
8 195312 5237773 191
9 97656 10475547 95
10 48828 20951093 48
11 24414 41902187 24

If period is set to one of these values, the full 10 bits of duty cycle is available. Past that, the Linux API will use the closest available value. Debug output can be enabled with:

echo "file pwm-ts.c +p" > /sys/kernel/debug/dynamic_debug/control

If this is enabled, the kernel can output additional information after setting a frequency:

echo 0 > /sys/class/pwm/pwmchip60/export
# 10ms period:
echo 10000000 > /sys/class/pwm/pwmchip60/pwm0/period
# 5ms duty cycle:
echo 5000000 > /sys/class/pwm/pwmchip60/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip60/pwm0/enable
dmesg | tail

This will output:

[ 1086.170695] ts-pwm e0000600.mikro_pwm: cycle=2049180 shift=10 cnt=976
[ 1086.170709] ts-pwm e0000600.mikro_pwm: shift=10 cnt=976 duty_cnt=49

[   75.758146] ts-pwm 500001a8.mikro_pwm: cycle=1293661 shift=10 cnt=773
[   75.758184] ts-pwm 500001a8.mikro_pwm: shift=10 cnt=773 duty_cnt=387

The last value in cnt indicates how much resolution is available for the duty cycle at this given period. In the best case there are 10 bits (0-2047) to specify duty cycle, but this above example is 0-976 to arrive at this particular period. You can determine the duty cycle increments with period / cnt.

1000000000 / 1023 = 977517

The duty cycle can then be configured in increments of 977517ns. Smaller values will round to the closest value.

This PWM will allow a max speed of 50MHz / 3 = 16.6MHz, but this will sacrifice all of the available duty cycle except an on/50%/off. The slowest speed is highest divisor at 24hz.

While the Linux driver is recommended for most users, the PWM core is located on the FPGA on PCIe BAR0 + 0x600.

Offset Bits Description
0x0 15:2 Reserved
1 Inversed (0 = idle high, duty cycle low), (1 = idle low, duty cycle high)
0 Enabled
0x2 15:10 Reserved
9:0 Period
0x4 15:10 Reserved
9:0 Duty Cycle
0x6 15:4 Reserved
3:0 shift (Clock frequency = 50000000 / (1 >> shift))