Reflow Toaster Oven Vid 3: MSP430 configuration
In this part I go over the MSP430 configuration for this project…ex: clocks, timers, ADC10, etc…
Here are all the register configurations which we will go over subsequently below:
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer // Configure Clock /* This section configures the main clock MCLK to 8Mhz from default * settings in the msp430 header file and divide the DCO (clock module) * by 8 for the sub main clock or SMCLK. The DCO sources both MCLK and * SMCLK. */ DCOCTL = CALDCO_8MHZ; // MCLK = 8Mhz BCSCTL1 = CALBC1_8MHZ; // Configure Submain clock for TimerA BCSCTL2 &= ~SELM0; // MCLK comes from DCO BCSCTL2 &= ~SELM1; BCSCTL2 &= ~SELS; // Sub main clock comes from DCO BCSCTL2 |= DIVS0 + DIVS1; // SMCLK = 1 MHz (DC0/8) // Enable IO for LEDs P1OUT &=~ BIT0; // clear P1.0 output bit (sets pin output bit to 0...off) P1DIR |= BIT0; // set P1.0 direction to be an output // Configure TimerA0 /* TimerA0 is setup to do three things. CCR0 controls temperature/power updates * for PWM value. CCR1 sends temperature data to be plotted by the graphing functionality * to the GUI. CCR2 sends data (room temp and thermal couple temp) updates to the GUI * that gets displayed in the text boxes on the main control interface. * * TimerA0's clock is SMCLK (1Mhz) divided by 8 to yield 125 kHz. We then load up * the capture compare registers with the number of clock ticks we want to wait * before interrupting. Since CCR0 interupts 50x per second we load it up with * 2500. CCR1 interrupts twice a second so its loaded with 62500. CCR2 interrupts * 5x per second so it's loaded with 25000. This is all the timing for the comm. links * to the GUI. * * This is also configured in up mode, which means the timer counts up to the value in CCR0 * and then the TAR (regester inremented per every TimerA0 clk cycle) is reset. */ TA0CTL |= TASSEL1; // Sets TimerA0 to use SMCLK as Timer clock TA0CTL |= ID0 + ID1; // Divide Timer clock by 8 to yield 125khz TA0CTL |= MC1; // set timer to up mode. // CCR0 TA0CCTL0 |= CCIE; // enable CCR0 interrupt TA0CCTL0 &=~ CCIFG; // clear CCR0 interrupt flag TA0CCR0 = 2500; // interrupt 50x / sec // CCR1 TA0CCTL1 |= CCIE; // enable CCR0 interrupt TA0CCTL1 &=~ CCIFG; // clear CCR0 interrupt flag TA0CCR1 = 62500; // interrupt 2x / sec // CCR2 TA0CCTL2 |= CCIE; // enable CCR0 interrupt TA0CCTL2 &=~ CCIFG; // clear CCR0 interrupt flag TA0CCR2 = 25000; // interrupt 5x / sec // Configure TimerA1 /* TimerA1 is configured to run our PWM signal. CCR0 defines the * periode and CCR1 defines the duty cycle. It is set to 125kHz * just like TimerA0 is. The difference here is that we set the * out put mode to OUTMOD_6 which configures the timer output * in toggle/set mode. * */ TA1CTL |= TASSEL1; // TimerA1 clock = SMCLK TA1CTL |= ID0 + ID1; // SMCLK/8 TA1CTL |= MC_1; // Configure timer as up mode // CCR0 TA1CCR0 = 25000; // interrupt 5x / sec // CCR1 TA1CCTL1 |= OUTMOD_6; // Toggle Set mode TA1CCR1 = 10; // Determines pulse width (this is basically initialising pulse width to a small duty cycle // Enable PWM Pins P2DIR |= BIT1; P2SEL |= BIT1; // Configure UART 9600 baud rate /* * The UCA0 UART with a baud rate of 9600 and interrupt capabilities. */ UCA0CTL1 |= UCSWRST; // Hold module in reset during configuration UCA0CTL0 &=~ (UCMODE0 + UCMODE1); // set UCA0 to uart mode UCA0CTL1 |= UCSSEL1; // Use SMCLK as UART module's clock UCA0BR0 = 0x68; // This and the value below come from the charts in the datasheet UCA0BR1 = 0x00; UCA0MCTL = UCBRS_1; // This is also used to configure baud rate...again taken from the table in the datasheet // Enable pins for UART P1SEL = BIT1 + BIT2; // This and the below instruction select what functionality the specific pin (tx,rx) P1SEL2 = BIT1 + BIT2; // have. This info is found in the device specific data sheet under port schematics P1DIR |= BIT2; // set tx pin as output UCA0CTL1 &= ~UCSWRST; // Take UCA0 module out of reset now that it is configured IE2 |= UCA0RXIE; // enable receive interrupt capability IE2 |= UCA0TXIE; // enable transmit interrupt capability // Setup ADC10 //P1DIR &=~ (BIT5 + BIT7); // dont' need //P1SEL &=~ (BIT5 + BIT7); // don't need //P1SEL2 &=~ (BIT5 + BIT7); // don't need //P1REN = 0x00; // don't need /* ADC10 sample resolution is configured below to 2mV/sample. * * INCH_11 -> sets input channel to "1011" or (Vcc-Vss)/2 * ADC100SSEL_3 -> Set ADC clock source to SMCLK * CONSEQ_1 -> Set conversion sequence mode to sequence of channels * ADC10DIV -> Sets ADC clock divider to divide by 1...i.e don't divide ADC clock/SMCLK * ADC10SHT_0 -> Adc sample and hold time...hold for 4 clock cycles (4uS) * ADC10IE -> Enable ADC interrupts * MSC -> Enables multiple sample and conversions (only useful for sequence of channels) * ADC10ON -> Turns on ADC10 module * SREF_2 -> Selects reference voltage to be VeREF+ for V+ and Vss for V-. * ADC10AE |= BIT5 + BIT7 * - Sets A5 and A7 to analog inputs (we are really only sampling these channels) * - Setting this means the ADC takes control of associated Ax pins * * ADC10DTC1 = 12 // Sets number of data transfers for Data Transfer Controller * ADC10SA // sets start address for DTC controller * * ADC10CTL0 |= ENC + ADC10SC; * - Enables conversions and starts the first conversion cycle */ ADC10CTL1 |= INCH_11 + ADC10SSEL_3 + CONSEQ_1 + ADC10DIV_0; ADC10CTL0 |= ADC10SHT_0 + ADC10IE + MSC + ADC10ON + SREF_2; // use VeeRef ADC10AE0 |= BIT5 + BIT7; ADC10DTC1 = 12; ADC10SA = (short)&adc[0]; // type cast pointer to int so we can store the value in the ADC10SA register ADC10CTL0 |= ENC + ADC10SC; _BIS_SR(GIE);
The first thing I set up is the clock system for the MSP430G2553. This is usually a good idea, but one warning for people using the DTC in high frequency mode (16 Mhz). Make sure you give the chip ample time to power up because if you rush the chip into 16Mhz clock speeds and you don’t supply power fast enough you can have intermittent power up problems. So back to the code at hand:
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer // Configure Clock /* This section configures the main clock MCLK to 8Mhz from default * settings in the msp430 header file and divide the DCO (clock module) * by 8 for the sub main clock or SMCLK. The DCO sources both MCLK and * SMCLK. */ DCOCTL = CALDCO_8MHZ; // MCLK = 8Mhz BCSCTL1 = CALBC1_8MHZ; // Configure Submain clock for TimerA BCSCTL2 &= ~SELM0; // MCLK comes from DCO BCSCTL2 &= ~SELM1; BCSCTL2 &= ~SELS; // Sub main clock comes from DCO BCSCTL2 |= DIVS0 + DIVS1; // SMCLK = 1 MHz (DC0/8)
The first instruction disables the watch dog timer. This bit (WDTHOLD) must be set with a password bit as well. This is to make sure you are aware you’re disabling the watch dog. Next I set the main control registers for the DCO (digitally controlled oscillator) to a preset setting (taken from the msp430g2553.h file as shown in the video) which effectively configures the DTC to a pretty accurate 8 Mhz. The SELM0 and SELM1 bits are reset to force the main clock (MCLK, i set these bits as good measure) to use the DCO. SELS is reset to force the submain clock to use the DCO as well. The last line sets the SMCLK divider. I did this because none of our peripherals will require a high speed clock. Breaking it down now allows me to use smaller counts/counters in the service routines as you will see. This system is actually so slow that I break the timer clocks down by another 8 later on.
// Enable IO for LEDs P1OUT &=~ BIT0; // clear P1.0 output bit (sets pin output bit to 0...off) P1DIR |= BIT0; // set P1.0 direction to be an output // Configure TimerA0 /* TimerA0 is setup to do three things. CCR0 controls temperature/power updates * for PWM value. CCR1 sends temperature data to be plotted by the graphing functionality * to the GUI. CCR2 sends data (room temp and thermal couple temp) updates to the GUI * that gets displayed in the text boxes on the main control interface. * * TimerA0's clock is SMCLK (1Mhz) divided by 8 to yield 125 kHz. We then load up * the capture compare registers with the number of clock ticks we want to wait * before interrupting. Since CCR0 interupts 50x per second we load it up with * 2500. CCR1 interrupts twice a second so its loaded with 62500. CCR2 interrupts * 5x per second so it's loaded with 25000. This is all the timing for the comm. links * to the GUI. * * This is also configured in up mode, which means the timer counts up to the value in CCR0 * and then the TAR (regester inremented per every TimerA0 clk cycle) is reset. */ TA0CTL |= TASSEL1; // Sets TimerA0 to use SMCLK as Timer clock TA0CTL |= ID0 + ID1; // Divide Timer clock by 8 to yield 125khz TA0CTL |= MC1; // set timer to up mode. // CCR0 TA0CCTL0 |= CCIE; // enable CCR0 interrupt TA0CCTL0 &=~ CCIFG; // clear CCR0 interrupt flag TA0CCR0 = 2500; // interrupt 50x / sec // CCR1 TA0CCTL1 |= CCIE; // enable CCR0 interrupt TA0CCTL1 &=~ CCIFG; // clear CCR0 interrupt flag TA0CCR1 = 62500; // interrupt 2x / sec // CCR2 TA0CCTL2 |= CCIE; // enable CCR0 interrupt TA0CCTL2 &=~ CCIFG; // clear CCR0 interrupt flag TA0CCR2 = 25000; // interrupt 5x / sec
In this next section I enable the red led on the launch pad so I can get a simple visual output. This is used in my TimerA0 themp control ISR.
The next big section here is the TimerA0 module. The first thing I do is configure the actual timer module by setting the clock divider as I mentioned. This just helps me out later on with my service routines/counts/counters etc… I also set the timer’s incoming clock to SMCLK and configure the time in up mode. Up mode makes the timer count up to CCR0 and then reset it’s count. The capture compare registers are pretty simple. All you do really is enable interrupts, clear the interrupt flag and set the number of counts that you want to count up to before interrupting in the capture compare registers. This is the same for all 3.
// Configure TimerA1 /* TimerA1 is configured to run our PWM signal. CCR0 defines the * periode and CCR1 defines the duty cycle. It is set to 125kHz * just like TimerA0 is. The difference here is that we set the * out put mode to OUTMOD_6 which configures the timer output * in toggle/set mode. * */ TA1CTL |= TASSEL1; // TimerA1 clock = SMCLK TA1CTL |= ID0 + ID1; // SMCLK/8 TA1CTL |= MC_1; // Configure timer as up mode // CCR0 TA1CCR0 = 25000; // interrupt 5x / sec // CCR1 TA1CCTL1 |= OUTMOD_6; // Toggle Set mode TA1CCR1 = 10; // Determines pulse width (this is basically initialising pulse width to a small duty cycle // Enable PWM Pins P2DIR |= BIT1; P2SEL |= BIT1;
The timer configuration for TimerA1 is a little bit more difficult because this is how we create our PWM signal. Once you understand whats going on though it isn’t too bad. The PWM output works by configuring TimerA1 as we did before, but rather than using the CCR registers to throw interrupts for processing we configure them to control the frequency and duty cycle. The clocks and divisions for this timer is the same as the one above, but CCR0 is used to control the frequency of the output waveform and CCR1 is used to control the duty cycle. In toggle set mode the output pin is controlled by hardware and out puts a signal that is high until we reach CCR1 and then goes low. This looks like a square wave at 50% duty cycle with a frequency of 125,000/CCR0 or 5Hz. Increasing and decreasing CCR1 increases and decrease where we reset the output effectively increasing and decreasing the duty cycle.
// Configure UART 9600 baud rate /* * The UCA0 UART with a baud rate of 9600 and interrupt capabilities. */ UCA0CTL1 |= UCSWRST; // Hold module in reset during configuration UCA0CTL0 &=~ (UCMODE0 + UCMODE1); // set UCA0 to uart mode UCA0CTL1 |= UCSSEL1; // Use SMCLK as UART module's clock UCA0BR0 = 0x68; // This and the value below come from the charts in the datasheet UCA0BR1 = 0x00; UCA0MCTL = UCBRS_1; // This is also used to configure baud rate...again taken from the table in the datasheet // Enable pins for UART P1SEL = BIT1 + BIT2; // This and the below instruction select what functionality the specific pin (tx,rx) P1SEL2 = BIT1 + BIT2; // have. This info is found in the device specific data sheet under port schematics P1DIR |= BIT2; // set tx pin as output UCA0CTL1 &= ~UCSWRST; // Take UCA0 module out of reset now that it is configured IE2 |= UCA0RXIE; // enable receive interrupt capability IE2 |= UCA0TXIE; // enable transmit interrupt capability
The UART is pretty simple and covered well in both the comments and the video. The most tricky part of this is the baud rate and you should really just look that up in the table in the datasheet. Also, the module should be held in reset while configuring and review the port schematics and tables in the device specific datasheet to figure out how to set the PSEL registers (or watch the video).
/* ADC10 sample resolution is configured below to 2mV/sample. * * INCH_11 -> sets input channel to "1011" or (Vcc-Vss)/2 * ADC100SSEL_3 -> Set ADC clock source to SMCLK * CONSEQ_1 -> Set conversion sequence mode to sequence of channels * ADC10DIV -> Sets ADC clock divider to divide by 1...i.e don't divide ADC clock/SMCLK * ADC10SHT_0 -> Adc sample and hold time...hold for 4 clock cycles (4uS) * ADC10IE -> Enable ADC interrupts * MSC -> Enables multiple sample and conversions (only useful for sequence of channels) * ADC10ON -> Turns on ADC10 module * SREF_2 -> Selects reference voltage to be VeREF+ for V+ and Vss for V-. * ADC10AE |= BIT5 + BIT7 * - Sets A5 and A7 to analog inputs (we are really only sampling these channels) * - Setting this means the ADC takes control of associated Ax pins * * ADC10DTC1 = 12 // Sets number of data transfers for Data Transfer Controller * ADC10SA // sets start address for DTC controller * * ADC10CTL0 |= ENC + ADC10SC; * - Enables conversions and starts the first conversion cycle */ ADC10CTL1 |= INCH_11 + ADC10SSEL_3 + CONSEQ_1 + ADC10DIV_0; ADC10CTL0 |= ADC10SHT_0 + ADC10IE + MSC + ADC10ON + SREF_2; // use VeeRef ADC10AE0 |= BIT5 + BIT7; ADC10DTC1 = 12; ADC10SA = (short)&adc[0]; // type cast pointer to int so we can store the value in the ADC10SA register ADC10CTL0 |= ENC + ADC10SC; _BIS_SR(GIE);
The analog to digital converter is a little complicated. I recommend watching this video as I won’t be able to describe it as well typing. The input channel is set to channel 12 because that is where we want to start our sampling sequence. I chose this because it gave me a good sample of Vcc/2 which I used to make sure my adc values were right since I had and external voltage reference. Since we are using the sequence of channels method the module will sample from your set channel down to A0 so that will give us 12 total samples hence the 12 member array. The only sample of interest though are A5 and A7 so I enable those as analog input pins to the module. The typecasting ( (short)&adc[0]) line is a bit tricky. We have to type case here because the compilers sees a data type of pointer returned and we want to store that in the ADC10SA (start address register for the DTC). To do this without the compiler getting mad we have to let it know that we are doing this on purpose and thats where typecasting comes in. We explicitly tell the compiler we are changing the pointer over to an integer and then store it in the register. After all this the configuration is done and we enable the ADC and start conversions.
The last instruction enables global interrupts. This is required to use peripheral interrupts.
Data Package:
Note-I haven’t been able to get the python app packed up as an exe yet because of some technical problems, but when I get this figured out I’ll update the package. The source code (python GUI and embedded C) is packed up as the aptana project and CCS studion project respectively. It should be simple to import these and check out the code. If importing doesn’t work just check out the source files or create your own project and add the source files only to it.