PWM on Msp430g2553

| Comments

Finally, the PWM! \o/

‘Light’ introduction into PWM on MCUs

Pulse-width-modulation is a method of generating analog signal from its digital counterparts. The square-formed digital signal is controlled by adjusting its on-off time-period, and another thing called duty-cycle. On MCUs this is done by using count-timers. The timer (counter) will count up to certain value (and then back to zero, over and over), this value is the period, and some register will keep another value to compare with the counter, this value will control/toggle the high (on) signal and latched it until the next comparison matched up, this another value represents our duty-cycle. Here’s example:

An MCU with 12KHz clock.
We will set a PWM with 1s period. It means we have to tell the timer (counter) to count up to 12000 (see it’s match with the clock).
We want a 75% duty cycle, which means in 25% of a single period the digital signal will be off. We need the 75% value for the compare register: 75/100 * 12000 = 9000 equals to ~750ms.

This is how we augment the analog signal.

The effect of this is we now have a wave function as an integral (area under the curve) of its discrete digital signal, and by controlling the period and its duty cycle we may obtain different voltage for the augmented analog signal.

MSP430 comes with varied configuration to control how the PWM signal behave. Including the count-mode: Up, Continuous, Up-Down. and ouput-mode: Set, Reset, Toggle, Set-Reset/Reset-Set. For a complete reference it’s available on the MSP430 Family guide.

So what’s with this PWM thing? Well it’s because most of the actuators devices actually driven by analog signals like motors, speakers, lamp/led dimmers, wireless communication link, etc. While MCUs/CPUs drive digital (discrete) signal, so to drive these devices (actuators) it’s either solving this with DAC, or PWM.

MSP430G2553 PWM Outputs

Unlike arduino-family which is based on Atmel AVR chips, msp430g2553 only has 3 pwm outputs max, or 5 if you’re able to work with continuous mode. While the arduinos has 6 PWM outputs or so.

Most (if not all) tutorials I saw never really mention which pins are able to output PWM signal, fortunately it’s mentioned vaguely at the datasheet.

It’s a bit confusing since the datasheet mentioned that this chip has 2 16bit-timers (Timer_A) with 3 capture/compare (c/c) registers each. While in fact my msp430g2553 only has 2 c/c registers for its timer_A0. It doesn’t clearly stated that the 20-PDIP package of the chip doesn’t have timer_A0.2 pin, apparently it’s only available on msp430g2553 with 28-TSSOP and 32-QFN packages :(.

To control the outputs we may select which pin related to which c/c register we used. As portrayed on the image above, Pin 1.2 can be selected to outputting PWM signal controlled by timer_A0.1.

As for the code, the simplest PWM control looks just like this: (adapted from CCS code example)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <msp430.h>

int main(void)
{
  WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
  P1DIR |= BIT2;                            // P1.2 output
  P1SEL |= BIT2;                            // P1.2 Select as TA0 output
  TA0CCR0 = 512-1;                          // PWM Period
  TA0CCTL1 = OUTMOD_7;                      // TA0CCR1 reset/set output mode
  TA0CCR1 = 384;                            // TA0CCR1 PWM duty cycle
  TA0CTL = TASSEL_2 + MC_1;                 // SMCLK, up mode

  __bis_SR_register(LPM0_bits);             // Enter LPM0
}

The comment should be clear enough, our first PWM set with the timer_A0 powered by SMCLK (at its default clock 1MHz). We used 512-count period (512µs), with 75% duty cycle (384µs), which will be outputted to the pin1.2.

We can try connecting pin1.2 to LED and see the LED light in dim, try adjusting the duty cycle (TA0CCR1) closer to (but not bigger than) its period value (TA0CCR0) and see the led emits more light. So this is how dimmers work. But that wasn’t even remotely interesting. So let’s try controlling a motor servo instead.

A Servo it is

So there’s 3 types of electric motors (AFAIC), a motor servo, a motor-stepper, and a DC motor (there’s 2 different dc motors, brushed and brushless, but we will leave it at that). What I currently have is a couple of cheap servos from hextronic, HXT900. So the example will be based on that.

To wire the servo with the launchpad board I used single 1K resistor and an LED this is act as a guard for the signal wire to the servo. It’ll much better if we have separated power supply for the servo, but now we’ll just hook the Vcc and Gnd wire directly to the board.

A servo rotates to certain angle when given a particular analog signal with certain frequency. In the case of HXT900, it needs analog signal with 50Hz frequency. To drive the servo we will adjust signal PWM variably and let the servo rotate to some certain angles. the max angle of the servo is 180° (or ±90°).

I’ve prepared some commons function in a single lib here. It’s still rough as it’s not even finished. But will do for our current demo. The LUT method was stolen and adapted from GuShH’s blog.

We will control the servo from the launchpad’s switch button, make use the exact debouncing code from the blinking fizzbuzz code last time. The servo will step at each cycle and we’ll increase the duty cycle as te servo rotates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#define MCU_CLOCK           1100000
#define PWM_FREQUENCY       50

#define SERVO_STEPS         180     // Maximum amount of steps in degrees (180 is common)
#define SERVO_MIN           450     // The minimum duty cycle for this servo
#define SERVO_MAX           2700    // The maximum duty cycle

#include <msp430_commons.c>

unsigned int duty_cycle_at[SERVO_STEPS + 1];
unsigned int PWM_Period = (MCU_CLOCK / PWM_FREQUENCY);  // PWM Period
unsigned int current_position, up;

void main (void) {
    // Shut off the watchdog (for now)
    WDTCTL = WDTPW + WDTHOLD;

    init_servo_angle_lut();

    Pin1_setup(P_OUT, BIT6);

    // Setup the PWM, with:
    // Up-mode & reset-set cycle
    PWM1_std_setup(PWM_Period, duty_cycle_at[0]);

    // select pin 1.2 for peripheral,
    // in this case for timer A as pwm output
    Pin1_select_peripheral(BIT2);

    // Setup P1.3, for triggering pwm
    Pin1_switch_setup(BIT3);

    current_position = 0;
    up = 0;

    __bis_SR_register(LPM0_bits | GIE);
}

// Port 1 interrupt service routine
#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
    P1IES ^= BIT3;

    // Debounce~
    // disable further interrupt from P1.3
    P1IE  &= ~BIT3;
    P1IFG  =  0;

    P1OUT ^= BIT6;
    up ^= 1;

    // Enable Watchdog
    // Select SMCLK as source clock.
    WDTCTL = WDT_MDLY_32;
    IFG1 &= ~WDTIFG;
    IE1  |=  WDTIE;
}

#pragma vector=WDT_VECTOR
__interrupt void watchdog_timer (void)
{
    // Re-enable button push sensing
    P1IE  |=  BIT3;
    P1IFG  =  0;

    // Stop watchdog
    WDTCTL = WDTPW | WDTHOLD;
    IE1   &= ~WDTIE;
}

#pragma vector=TIMER0_A1_VECTOR
__interrupt void t0a1_isr (void)
{
    if (up && (current_position < 180)) {
        TA0CCR1 = duty_cycle_at[++current_position];    // step up
    } else if (current_position > 0) {
        TA0CCR1 = duty_cycle_at[--current_position];    // step down
    }
    TA0CCTL1 &= ~CCIFG;
}

// to be compiled with CCS & TI C compiler

But Stepping isn’t servo good point, should use motor stepper for that. In addition the ISR has 11 cycles latency, and an update at each end of PWM cycles make the servo rotates less smoothly, and slow. Servo more suited for angle-based rotation. so it’s better to directly tell it to move to certain angles rather than stepping to that angle. As usual the above code, and the more smoother servo rotation is available here.

Here’s some crappy demo video of the code (using the servo_pwm.c code not the above)

The sound is a bit out of sync blame my 5-years old phone. But as showed on the video when we push the switch button the servo will rotates 180° before stop, it will rotate back to its start position only when the button is released. With better controller (as joystick) we can control the servo with more precision and hold the servo on any particular position. \o/ excitement!

Comments