A Hardware PWM Implementation for Cubie and Allwinner A10

- - posted in Hardware, Linux, PWM

Cubieboard Cubieboard-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.

PWM signals

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:

1
2
pinMode(5,OUTPUT);    // Declare pin 5 as output
analogWrite(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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void 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 Arduino

The 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/period
      echo 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

PWM signals

PWM signals

  • 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:

1
2
echo 50 > /sys/class/pwm-sunxi/pwm0/duty_percent
echo 1 > /sys/class/pwm-sunxi/pwm0/run

Or a 25% duty cycle at 250hz with the following code:

1
2
3
echo 250hz > /sys/class/pwm-sunxi/pwm0/period
echo 50 > /sys/class/pwm-sunxi/pwm0/duty_percent
echo 1 > /sys/class/pwm-sunxi/pwm0/run

Or a 25% duty cycle at 500hz with negative polarity with the following code:

1
2
3
4
echo 500hz > /sys/class/pwm-sunxi/pwm0/period
echo 50 > /sys/class/pwm-sunxi/pwm0/duty\_percent
echo 0 > /sys/class/pwm-sunxi/pwm0/polarity
echo 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

Comments