Jump to content
43oh

15 channel PWM with async serial control


Recommended Posts

This is derived from 16 channel PWM with a single timer.

 

The ISR has be extended to handle PWM and async serial reception. The first timer capture/compare is used for timing the serial bits and the second is used for PWM.

 

This is the ISR...

__interrupt void Timer0_A1(void)
{
   static unsigned bit_mask = 0;           // Serial rx data bitmask
   static unsigned rx_data;                // Serial rx data
                                           //
   const unsigned x = TAIV;                // Clear interrupt flag, get vector
                                           //
   if(x == 4) {                            // --- CCR2 - PWM
       P1OUT &= ~pa->port_off;             // Port pins off
       P2OUT &= ~pa->port_off >> 8;        //
       P1OUT |= pa->port_on;               // Port pins on
       P2OUT |= pa->port_on >> 8;          //
       pa = pa->next;                      // Next node in list
       TACCR2 = pa->time;                  // Update timer compare time
                                           //
   } else if(x == 2) {                     // --- CCR1 - Serial rx
       if(TACCTL1 & CAP) {                 // Start bit (capture mode)
           rx_data = 0;                    // Clear rx data
           bit_mask = 1;                   // Begin with bit 0
           TACCTL1 &= ~CAP;                // Switch from capture to compare mode
           TACCR1 += 2500;                 // Set timer for middle of first data bit
       } else {                            // Data/stop (compare mode)
           if(bit_mask & 0x0100) {         // Stop bit?
               *head++ = rx_data;          // Add rx data to ring buffer
               if(head >= &rx_buf[sizeof(rx_buf)/sizeof(rx_buf[0])]) head = rx_buf;
               TACCTL1 |= CAP;             // Switch from compare to capture mode
           } else {                        // Data bit
               if(TACCTL1 & SCCI) rx_data |= bit_mask; // Update rx data
               bit_mask <<= 1;             // Next data bit
               TACCR1 += 1667;             // Set timer for middle of next bit
           }
       }
   }
}

 

The PWM code is unchanged, but now is only executed when the interrupt vector indicates capture/compare 2 match.

The serial code begins in capture mode. When a falling edge is detected the mode is changed to compare to sample in the middle of each data bit and the stop bit. A ring buffer is used to prevent loss of received data.

 

Two test cases are provided. The first allows the brightness of the Lauchpads LED to be set using 0 to 9 and a to j.

The second test case implements a simple serial protocol to set the PWM value of all 15 outputs. The packet format is...

0x55 0xAA PWM0 PWM1 ...... PWM13 PWM14 Checksum

Addressing could easily be added to allow multiple MSP430 on the same serial line.

 

Since this code uses only a single TimerA module, it can run on any MSP430 with a TimerA.

 

#include "msp430g2553.h"
#include "string.h"

typedef struct {                            // PWM ISR linked list node struct
   unsigned time;                          // Time for on/off action to occur
   unsigned port_off;                      // Port pins to turn off
   unsigned port_on;                       // Port pins to turn on
   void *next;                             // Next node in linked list
} TPWM;                                     //

TPWM pw[16], *pi;                           // Array and inactive list head
volatile TPWM *pa;                          // Active node

unsigned char rx_buf[32];
unsigned char *head, *tail;

void pwm_set(const unsigned pin, const unsigned time)
{
   const unsigned mask = 1 << pin;         //
   const unsigned tm = time & 0xFF00;      // Limit closeness of entries
   TPWM *b, *p, *n;                        //
                                           //
                                           // -- Try to find existing active node for this pin
   for(b = &pw[0], p = b->next; p != &pw[0]; b = p, p = b->next) {
       if(p->port_off & mask) {            // Found it
           if(p->time == tm) return;       // - Time is the same, nothing to do, return...
           while(pa != p);                 // Wait for this node to be active
           while(pa == p);                 // Wait for this node to become inactive
                                           // Safe to remove now
           if(p->port_off == mask) {       // - Node is only used for this pin, remove it
               b->next = p->next;          // Remove link
               p->next = pi;               // Add to inactive list
               pi = p;                     //
           } else {                        // - Node is used for multiple pins
               p->port_off &= ~mask;       // Remove this pin from the node
           }                               //
           break;                          // Found the pin, so stop searching
       }                                   //
   }                                       //
                                           // - Update first node in list
   if(tm == 0) {                           // If time is 0, turn off and return
       pw[0].port_on &= ~mask;             //
       pw[0].port_off |= mask;             //
       return;                             //
   } else {                                // If time is non-zero, turn on
       pw[0].port_on |= mask;              //
       pw[0].port_off &= ~mask;            //
       if(time == 0xFFFF) return;          // If max, no need to turn off, so return
   }                                       //
                                           //
                                           // Find where the new turn off node will go
   for(b = &pw[0], p = b->next; p != &pw[0]; b = p, p = b->next)
       if(p->time >= tm) break;            // Stop when an entry of >= time is found
                                           //
   if(p->time == tm) {                     // If same time, use existing node
       p->port_off |= mask;                // Add this pin
       return;                             // All done...
   }                                       //
                                           //
   n = pi;                                 // Get a node from the inactive list
   pi = n->next;                           //
                                           //
   n->port_off = mask;                     // Setup new node
   n->port_on = 0;                         //
   n->time = tm;                           //
                                           //
   n->next = p;                            // Insert node in to active list
   b->next = n;                            //
}

void pwm_init(void)
{
   unsigned n;
   memset(pw, 0, sizeof(pw));              // Clear entire array
   pa = &pw[0];                            // Active list always begins with first array element
   pa->next = &pw[0];                      // Begin with only 1 node in list
   pi = &pw[1];                            // First inactive node is second array element
   for(n = 1; n < sizeof(pw)/sizeof(TPWM) - 1; ++n) // Link the inactive nodes
       pw[n].next = &pw[n + 1];            //
                                           //
   TACTL = TASSEL_2 + MC_2 + ID_0;         // Setup timer, continuous mode, SMCLK/1
   TACCTL2 = CCIE;                         // Enable timer interrupt
   _EINT();                                // Enable interrupts
}

#pragma vector = TIMER0_A1_VECTOR
__interrupt void Timer0_A1(void)
{
   static unsigned bit_mask = 0;           // Serial rx data bitmask
   static unsigned rx_data;                // Serial rx data
                                           //
   const unsigned x = TAIV;                // Clear interrupt flag, get vector
                                           //
   if(x == 4) {                            // --- CCR2 - PWM
       P1OUT &= ~pa->port_off;             // Port pins off
       P2OUT &= ~pa->port_off >> 8;        //
       P1OUT |= pa->port_on;               // Port pins on
       P2OUT |= pa->port_on >> 8;          //
       pa = pa->next;                      // Next node in list
       TACCR2 = pa->time;                  // Update timer compare time
                                           //
   } else if(x == 2) {                     // --- CCR1 - Serial rx
       if(TACCTL1 & CAP) {                 // Start bit (capture mode)
           rx_data = 0;                    // Clear rx data
           bit_mask = 1;                   // Begin with bit 0
           TACCTL1 &= ~CAP;                // Switch from capture to compare mode
           TACCR1 += 2500;                 // Set timer for middle of first data bit
       } else {                            // Data/stop (compare mode)
           if(bit_mask & 0x0100) {         // Stop bit?
               *head++ = rx_data;          // Add rx data to ring buffer
               if(head >= &rx_buf[sizeof(rx_buf)/sizeof(rx_buf[0])]) head = rx_buf;
               TACCTL1 |= CAP;             // Switch from compare to capture mode
           } else {                        // Data bit
               if(TACCTL1 & SCCI) rx_data |= bit_mask; // Update rx data
               bit_mask <<= 1;             // Next data bit
               TACCR1 += 1667;             // Set timer for middle of next bit
           }
       }
   }
}

unsigned char get_rx(void)
{
   const unsigned char c = *tail++;
   if(tail >= &rx_buf[sizeof(rx_buf)/sizeof(rx_buf[0])]) tail = rx_buf;
   return c;
}

void protocol(void)
{
   static unsigned state = 0;
   unsigned n;
   unsigned char pwm[15];
   unsigned char checksum;
   unsigned char c;

   for(; {
       c = get_rx();
       switch(state) {
           case 0:                         // First byte of packet must be 0x55
               if(c == 0x55) ++state;
               break;
           case 1:                         // Second byte of packet must be 0xAA
               if(c == 0xAA) {
                   n = 0;
                   checksum = 0;
                   ++state;
               } else {
                   state = 0;
               }
               break;
           case 2:                         // Get 15 PWM bytes
               pwm[n++] = c;
               checksum += c;
               if(n >= 15) ++state;
               break;
           case 3:                         // Get checksum
               if(c == checksum) {         // Update PWM if checksum is valid
                   for(n = 0; n < 15; ++n) {
                       pwm_set((n < 2) ? n : n + 1, (unsigned)pwm[n] << 8);
                   }
               }
               state = 0;                  // Next packet
               break;
       }
   }
}

void main(void)
{
   unsigned char c;

   WDTCTL = WDTPW | WDTHOLD;               // Disable watchdog
   DCOCTL = 0;                             // Run at 16 MHz
   BCSCTL1 = CALBC1_16MHZ;                 //
   DCOCTL = CALDCO_16MHZ;                  //
   P1OUT = P2OUT = 0;                      //
   P1DIR = 0xFF ^ BIT2;                    // P1.2 is serial in, all other output
   P1SEL = BIT2;                           // Enable capture interrupt
   P2SEL = 0;                              // Allow P2.6 and P2.7 to be used as GPIO
   P2DIR = 0xFF;                           // Enable all P2 pins as output

   pwm_init();                             // Initialize software PWM

   TACCTL1 = CM_2 | CAP | CCIE;            // Enable capture on falling edge
   head = tail = rx_buf;                   // Init rx ring buffer pointers

#if 0
   protocol();                             // Serial control of all 15 PWM outputs
#else   
   for(; {                               // Control LP LEDs with 0-9 and a-j
       if(head != tail) {
           c = get_rx();
           if(c >= '0' && c <= '9') {
               pwm_set(0, (c - '0') * 7000);
           } else if(c >= 'a' && c <= 'j') {
               pwm_set(6, (c - 'a') * 7000);
           }
       }
   }
#endif
}

Link to post
Share on other sites
  • 6 months later...
  • 11 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...