A Hardware PWM Implementation for Cubie and Allwinner A10
by
David H. Wilkins - Aug-23-2013 - posted in
Hardware,
Linux,
PWMCubieboard-1 - powered by the Allwinner A10 Arm SOC
When I received my first Allwinner A10 board it lacked support for hardware PWM. Without a driver for hardware PWM it’s very hard to use this very powerful SOC for controlling motors
This article describes the design of one such
hardware pwm driver interface.
The Arduino has a simple to use PWM interface and other implementations should strive to have a similarly developer friendly interface. The “analogWrite” Arduino interface is both easy to understand and implement. For instance the following code implements a 50% duty cycle on digital pin 5:
12pinMode(5,OUTPUT); // Declare pin 5 as outputanalogWrite(5,128); // Output a valud between 0 (off) and 255 (full on)
One problem with the Arduino interface is the learning curve required to venture beyond the primary interface. For instance, the above code doesn’t specify the PWM period. Many motors (servos..) function best within a specific PWM period.
This implementation doesn’t allow specification of the signal polarity either. If the hardware interface requires an inverted signal, then duty will have to be computed as 1/duty manually.
12345678910111213141516171819void TimerOne::setPeriod(long microseconds) // AR modified for atomic access{ long cycles = (F_CPU / 2000000) * microseconds; // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2 if(cycles < RESOLUTION) clockSelectBits = _BV(CS10); // no prescale, full xtal else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11); // prescale by /8 else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10); // prescale by /64 else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12); // prescale by /256 else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10); // prescale by /1024 else cycles = RESOLUTION - 1, clockSelectBits = _BV(CS12) | _BV(CS10); // request was out of bounds, set as maximum oldSREG = SREG; cli(); // Disable interrupts for 16 bit register access ICR1 = pwmPeriod = cycles; // ICR1 is TOP in p & f correct pwm mode SREG = oldSREG; TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); TCCR1B |= clockSelectBits; // reset clock select register, and starts the clock}
*example code from
Timer One Library for ArduinoThe Linux OS and expanded ram of the
cubieboard make possible a much more rich interface than the arduino affords. File based programming interfaces are available to extend the PWM control to many more languages. One possible implementation might include the following interface (examples in shell script):
/sys/class/pwm-sunxi/pwmX
This is the sysfs directory that contains the PWM interface. X is the PWM channel. The Allwinner A10 has 2
period (r/w)
period that makes up a cycle. Can be expressed as hz, khz, ms, or us. Whole numbers only. Examples: echo 10hz > /sys/class/pwm-sunxi/pwm0/periodecho 1khz > /sys/class/pwm-sunxi/pwm0/period
echo 100ms > /sys/class/pwm-sunxi/pwm0/period
echo 100us > /sys/class/pwm-sunxi/pwm0/period
echo 150khz > /sys/class/pwm-sunxi/pwm0/period
duty (r/w)
portion of the period above that is “active” or on. Same units as duty.
echo 100ms > /sys/class/pwm-sunxi/pwm0/period # Period is 100 milliseconds
echo 25ms > /sys/class/pwm-sunxi/pwm0/duty # 25% duty
echo 50ms > /sys/class/pwm-sunxi/pwm0/duty # 50% duty
echo 75ms > /sys/class/pwm-sunxi/pwm0/duty # 75% duty
duty_percent (r/w)
duty expressed as a percentage. Whole numbers only
echo 100ms > /sys/class/pwm-sunxi/pwm0/period # Period is 100 milliseconds
echo 25 > /sys/class/pwm-sunxi/pwm0/duty_percent # 25% duty
echo 50 > /sys/class/pwm-sunxi/pwm0/duty_percent # 50% duty
echo 75 > /sys/class/pwm-sunxi/pwm0/duty_percent # 75% duty
polarity (r/w)
Polarity of the pin during the duty portion. Note that the opposite state will be for the non-duty portion.
1 = high, 0 = low
pulse (r/w)
Output one pulse at the specified period and duty
echo 1 > /sys/class/pwm-sunxi/pwm0/puls # Output 1 pulse when run is 1
pin (r/o)
Name of the A10 pin this pwm outputs on. This is hardwired and informational only. Example:
cat /sys/class/pwm-sunxi/pwm0/pin # output: PB2
run (r/w)
Enable the PWM with the previously set parameters. Example:
echo 1 > /sys/class/pwm-sunxi/pwm0/run # Start the pwm interface
echo 0 > /sys/class/pwm-sunxi/pwm0/run # Stop the pwm interface
So, the above interface as a Linux kernel module can implement a 50% duty cycle hardware pwm with the following code:
12echo 50 > /sys/class/pwm-sunxi/pwm0/duty_percentecho 1 > /sys/class/pwm-sunxi/pwm0/run
Or a 25% duty cycle at 250hz with the following code:
123echo 250hz > /sys/class/pwm-sunxi/pwm0/periodecho 50 > /sys/class/pwm-sunxi/pwm0/duty_percentecho 1 > /sys/class/pwm-sunxi/pwm0/run
Or a 25% duty cycle at 500hz with negative polarity with the following code:
1234echo 500hz > /sys/class/pwm-sunxi/pwm0/periodecho 50 > /sys/class/pwm-sunxi/pwm0/duty\_percentecho 0 > /sys/class/pwm-sunxi/pwm0/polarityecho 1 > /sys/class/pwm-sunxi/pwm0/run
So, the design goal of a simple interface is achieved, with incremental functionality requiring only incremental amounts of additional code.
Next article – developing the
pwm-sunxi pwm module