The Rusty Spike

A Railroad Fan's Website

By

CMRI on a LP with 2x Shift Registers and 6x 16KHz PWM

lp4mrr - CMRI - PWM - Emulation on a MPS430

In my continuing research into CMRI Emulation on an MSP430G2553 chip, I recently tackled a few new challenges. Firstly, I wanted to expand the IO of the MSP430 via shift registers and control those pins from JMRI. Secondly, I wanted to address the issue of smoothly render a 16KHz PWM signal while simultaneously performing other tasks. To date, I have had success with PWM only when the MSP430 is performing just the PWM calcs and no other tasks. Previous attempts at using algorithm based PWM have resulted in jittery flashing on the LED being controlled by PWM while there are other items being processed by the MSP430.

The most recent sets of experiments have been a major success. Short video:

CMRI + 16KHz PWM Proof of Concept from claymore1977 on Vimeo.

Expanding IO

Expanding the output count was relatively easy, as I have worked with Shiftout registers before. I was planning on also testing input when I realized I had no Shiftin registers! Well, they’re on order now, but the slow boat from China is just that… slow. Anyways, I’d addressed the ShiftOut functionality in the past and have incorporated it into my LP4MRR header file. Once the byte(s) are received from the serial IO, shifting them out to the registers is as simple as:

1
2
	P1_SHIFT_OUT_8(first_byte,  LATCH_PIN, CLOCK_PIN, DATA_PIN);
	P1_SHIFT_OUT_8(second_byte, LATCH_PIN, CLOCK_PIN, DATA_PIN);

When the most recent order of ShiftIn and ShiftOut registers arrive, I’m going to try to expand the Launchpad out to a full SMINI and have 24 inputs and 48 outputs. That’s gonna be a fun project 🙂

 

PWM

The most recent round of experimentation led me to realize two things:

  1. Using a Graphics Rendering style ‘main loop’ works great. Minimize the amount of work done in interrupt handlers. Set flags and return quickly.
  2. Don’t spend precious MSP430 cpu cycles computing PWM values, use lookup tables.

Example #1: Given this sample PWM code:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/******************************************
 * Lookup Based PWM (Simple)
 *
 * COPYRIGHT 2013 Dave Loman
 *
 * Provided  under a:
 *    Creative Commons Attribution,
 *    Non-Commercial
 *    Share-Alike,
 *    3.0 Unported License
 *
******************************************/
 
#include <msp430.h>
#include "lp4mrr.h"
 
/* PWM */
unsigned char pwmPin = PIN0;
unsigned char lightStep = 0;
unsigned char dutyCycle = 0;
 
static const unsigned char CYCLE_01_LEN = 255;
static const unsigned char CYCLE_01[] = {
		//RAMP UP
		0, 7, 15, 22, 30, 37, 45, 52, 59, 66, 73,
		79, 86, 92, 99, 105, 111, 117, 123, 128, 134,
		139, 145, 150, 155, 160, 165, 169, 174, 178,
		183, 187, 191, 195, 199, 202, 206, 209, 213,
		216, 219, 222, 225, 227, 230, 232, 235, 237,
		239, 241, 243, 244, 246, 247, 249, 250, 251,
		252, 253, 253, 254, 254, 255, 255,
 
		//ON
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
 
		//RAMP Down
		255, 247, 239, 232, 224, 217, 209, 202, 195,
		188, 181, 175, 168, 162, 155, 149, 143, 137,
		131, 126, 120, 115, 109, 104, 99, 94, 89, 85,
		80, 76, 71, 67, 63, 59, 55, 52, 48, 45, 41,
		38, 35, 32, 29, 27, 24, 22, 19, 17, 15, 13,
		11, 10, 8, 7, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0,
 
		//OFF
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
 
/**
 * Main routine
 */
int main(void) {
	/* Stop Watchdog and set main clock to 16MHZ */
	WDTCTL = WDTPW + WDTHOLD;
	BCSCTL1 = CALBC1_16MHZ;
	DCOCTL = CALDCO_16MHZ;
 
	/*Configure pins as Inputs and/or Outputs */
	P1_DIR_OUT(PIN0);
 
	/* Setup Timer */
	TA1CCR0 = 600;	// Count limit (16 bit value, so 65536 max)
	TA1CCTL0 = CCIE; // Enable counter interrupts
	TA1CTL = TASSEL_2 + MC_1; // TASSEL_2 means use SMCLK,
				  // MC_1 means count UP
 
	/* Enable interrupts */
	__bis_SR_register(GIE);
}
 
/*  INTERRUPT HANDLERS  */
#pragma vector=TIMER1_A0_VECTOR
__interrupt void Timer1_A0 (void) {
 
	/* Increment and compare the dutyCyle to the lightStep */
	if (++dutyCycle >= CYCLE_01[lightStep]) {
		P1_PIN_OFF(pwmPin);
	} else {
		P1_PIN_ON(pwmPin);
	}
 
	/* If the 8 bit dutyCycle is about to roll over from */
	/* 255 back to 0, increment the lightstep */
	if (dutyCycle == 0xFF) {
		/* Range check the lightstep value */
		if (++lightStep > CYCLE_01_LEN) 
			lightStep = 0;
	}
}

We can see that the look up table is a static const. This will tell the compiler and run time to NOT load that into RAM. Instead, write it as data to the flash memory and read it from there. Since there is only 512 Bytes of RAM but up to 16K of Flash on the MSP430 and Flash is still pretty fast to read from, this works out amazingly well.

How does this PWM code work? It’s rather simple, actually. The dutyCycle continually counts from 0 (0x00) to 255 (0xFF). Each time the dutyCycle increases, it’s value is compared to a value from the look up table. If the dutyCycle is below that value, the LED is turned on. If the dutyCycle is above that value, the LED is turned off. This means that if the value from the look up table is 204, then the LED will be on 80% of the time (204/255*100). This might not exactly equate to 80% LED intensity, but it will be close. A lookup table value of 64 is about 25% intensity, 32 is 12.5%, etc.

Rather than using an algorithm to calculate the value to compare to the dutyCycle, the lightStep variable is used to retrieve value. Since the values are pre-computed, this is much, much faster, and allows for custom ‘lighting curves’. For example, incandescent bulbs are not as fast to turn on or off as an LED and this could be simulated with a custom ‘lighting curve’. The ‘lighting curve’ in the above example is a slower pulsing effect. Things like a double pulse gyralight are completely possible as well.

Example #2: Here’s code that uses two different look uptables to demonstrate two different lighting effects used simultaneously:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/******************************************
 * Lookup Based PWM (Simple)
 *
 * COPYRIGHT 2013 Dave Loman
 *
 * Provided  under a:
 *    Creative Commons Attribution,
 *    Non-Commercial
 *    Share-Alike,
 *    3.0 Unported License
 *
******************************************/
 
#include <msp430.h>
#include "lp4mrr.h"
 
/* PWM */
unsigned char pwmPin00 = PIN0;
unsigned char lightStep00 = 0;
unsigned char dutyCycle00 = 0;
unsigned char pwmPin01 = PIN6;
unsigned char lightStep01 = 0;
unsigned char dutyCycle01 = 0;
 
static const unsigned char CYCLE_01_LEN = 255;
static const unsigned char CYCLE_01[] = {
		//RAMP UP
		0, 7, 15, 22, 30, 37, 45, 52, 59, 66, 73,
		79, 86, 92, 99, 105, 111, 117, 123, 128, 134,
		139, 145, 150, 155, 160, 165, 169, 174, 178,
		183, 187, 191, 195, 199, 202, 206, 209, 213,
		216, 219, 222, 225, 227, 230, 232, 235, 237,
		239, 241, 243, 244, 246, 247, 249, 250, 251,
		252, 253, 253, 254, 254, 255, 255,
 
		//ON
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
 
		//RAMP Down
		255, 247, 239, 232, 224, 217, 209, 202, 195,
		188, 181, 175, 168, 162, 155, 149, 143, 137,
		131, 126, 120, 115, 109, 104, 99, 94, 89, 85,
		80, 76, 71, 67, 63, 59, 55, 52, 48, 45, 41,
		38, 35, 32, 29, 27, 24, 22, 19, 17, 15, 13,
		11, 10, 8, 7, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0,
 
		//OFF
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
 
static const unsigned char CYCLE_03_LEN = 127;
static const unsigned char CYCLE_03[] = {
 
		// RAMP UP
		0, 36, 72, 108, 144, 180, 204, 216, 228, 236,
		242, 246, 250, 252, 254, 255, 255, 255, 255,
 
		// ON
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
 
		// RAMP DOWN
		255, 219, 183, 147, 111, 75, 51, 39, 27, 19,
		13, 9, 5, 3, 1, 0, 0, 0, 0,
 
		// OFF
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
 
 
/**
 * Main routine
 */
int main(void) {
	/* Stop Watchdog and set main clock to 16MHZ */
	WDTCTL = WDTPW + WDTHOLD;
	BCSCTL1 = CALBC1_16MHZ;
	DCOCTL = CALDCO_16MHZ;
 
	/* Configure pins as Inputs and/or Outputs */
	P1_DIR_OUT(pwmPin00);
	P1_DIR_OUT(pwmPin01);
 
	/* Setup Timer */
	TA1CCR0 = 600;	// Count limit (16 bit value, so 65536 max)
	TA1CCTL0 = CCIE; // Enable counter interrupts
	TA1CTL = TASSEL_2 + MC_1; // TASSEL_2 means use SMCLK,
							  //MC_1 means count UP
 
	/* Enable interrupts */
	__bis_SR_register(GIE);
}
 
/*  INTERRUPT HANDLERS  */
#pragma vector=TIMER1_A0_VECTOR
__interrupt void Timer1_A0 (void) {
 
	/* Increment and compare the dutyCyle to the lightStep */
	if (++dutyCycle00 >= CYCLE_01[lightStep00]) {
		P1_PIN_OFF(pwmPin00);
	} else {
		P1_PIN_ON(pwmPin00);
	}
 
	/* If the 8 bit dutyCycle is about to roll over from */
	/* 255 back to 0, increment the lightstep */
	if (dutyCycle00 == 0xFF) {
		/* Range check the lightstep value */
		if (++lightStep00 > CYCLE_01_LEN)
			lightStep00 = 0;
	}
 
 
	/* Increment and compare the dutyCyle to the lightStep */
	if (++dutyCycle01 >= CYCLE_03[lightStep01]) {
		P1_PIN_OFF(pwmPin01);
	} else {
		P1_PIN_ON(pwmPin01);
	}
 
	/* If the 8 bit dutyCycle is about to roll over from */
	/* 255 back to 0, increment the lightstep */
	if (dutyCycle01 == 0xFF) {
		/* Range check the lightstep value */
		if (++lightStep01 > CYCLE_03_LEN)
			lightStep01 = 0;
	}
}

 

Example #3: This example is slightly different from #2 in that TA1CCR0 was lowered to 500, which will result in a slightly faster running dutyCycle, both PINs are being driven from the same Lookup Table, and finally, lightStep00 is set at the halfway point of the light curve. The result is the alternating of the RED and GREEN leds on the Launchpad in a fashion that simulates a grade crossing using incandescent lights:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/******************************************
 * Lookup Based PWM (Simple)
 *
 * COPYRIGHT 2013 Dave Loman
 *
 * Provided  under a:
 *    Creative Commons Attribution,
 *    Non-Commercial
 *    Share-Alike,
 *    3.0 Unported License
 *
******************************************/
 
#include <msp430.h>
#include "lp4mrr.h"
 
/* PWM */
unsigned char pwmPin00 = PIN0;
unsigned char lightStep00 = 64;
unsigned char dutyCycle00 = 0;
unsigned char pwmPin01 = PIN6;
unsigned char lightStep01 = 0;
unsigned char dutyCycle01 = 0;
 
static const unsigned char CYCLE_03_LEN = 127;
static const unsigned char CYCLE_03[] = {
 
		// RAMP UP
		0, 36, 72, 108, 144, 180, 204, 216, 228, 236,
		242, 246, 250, 252, 254, 255, 255, 255, 255,
 
		// ON
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255, 255, 255, 255, 255,
 
		// RAMP DOWN
		255, 219, 183, 147, 111, 75, 51, 39, 27, 19,
		13, 9, 5, 3, 1, 0, 0, 0, 0,
 
		// OFF
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
 
 
/**
 * Main routine
 */
int main(void) {
	/* Stop Watchdog and set main clock to 16MHZ */
	WDTCTL = WDTPW + WDTHOLD;
	BCSCTL1 = CALBC1_16MHZ;
	DCOCTL = CALDCO_16MHZ;
 
	/* Configure pins as Inputs and/or Outputs */
	P1_DIR_OUT(pwmPin00);
	P1_DIR_OUT(pwmPin01);
 
	/* Setup Timer */
	TA1CCR0 = 500;	// Count limit (16 bit value, so 65536 max)
	TA1CCTL0 = CCIE; // Enable counter interrupts
	TA1CTL = TASSEL_2 + MC_1; // TASSEL_2 means use SMCLK,
							  //MC_1 means count UP
 
	/* Enable interrupts */
	__bis_SR_register(GIE);
}
 
/*  INTERRUPT HANDLERS  */
#pragma vector=TIMER1_A0_VECTOR
__interrupt void Timer1_A0 (void) {
 
	/* Increment and compare the dutyCyle to the lightStep */
	if (++dutyCycle00 >= CYCLE_03[lightStep00]) {
		P1_PIN_OFF(pwmPin00);
	} else {
		P1_PIN_ON(pwmPin00);
	}
 
	/* If the 8 bit dutyCycle is about to roll over from */
	/* 255 back to 0, increment the lightstep */
	if (dutyCycle00 == 0xFF) {
		/* Range check the lightstep value */
		if (++lightStep00 > CYCLE_03_LEN)
			lightStep00 = 0;
	}
 
 
	/* Increment and compare the dutyCyle to the lightStep */
	if (++dutyCycle01 >= CYCLE_03[lightStep01]) {
		P1_PIN_OFF(pwmPin01);
	} else {
		P1_PIN_ON(pwmPin01);
	}
 
	/* If the 8 bit dutyCycle is about to roll over from */
	/* 255 back to 0, increment the lightstep */
	if (dutyCycle01 == 0xFF) {
		/* Range check the lightstep value */
		if (++lightStep01 > CYCLE_03_LEN)
			lightStep01 = 0;
	}
}

 

Please note that all of these examples require the inclusion of my LP4MRR header file.

Leave a Reply

Your email address will not be published. Required fields are marked *