Serial control of 14 dimmable LEDs

Serial communication on the Launchpad uses two of the pins (P1.1 and P1.2, the ones labeled UART on the board).

We can use the serial port to control the remaining 14 pins, sending characters to the Launchpad to change the brightness of the LEDs we attach to those pins. We use software Pulse Width Modulation so we are not limited to three pins as we would be if we used the timers to do the PWM.

The program for doing 14 pin PWM controlled by the serial port is shown below:


unsigned short values[16];
unsigned short limits[16];

enum { RESOLUTION=512 };

void
setup( void )
{
  P1DIR = 0b11111111;
  P2DIR = 0b11111111;
  
  Serial.begin( 9600 );
  
  for( unsigned x = 0; x < 16; x++ )
    limits[x] = x * x + 1;
}

char cmd[4];
unsigned in = 0;

int
digit( char c )
{
  if( c >= '0' && c <= '9' )
    return c - '0';
  else if( c >= 'A' && c <= 'F' )
    return 10 + c - 'A';
  else if( c >= 'a' && c <= 'f' )
    return 10 + c - 'a';
  else
    return -1;
}

void
tick( void )
{
  for( unsigned x = 0; x < 8; x++ )
  {
    if( values[x] == limits[x] )
      P1OUT &= ~(1 << x);                     // pin goes low
    else if( values[x] == 0 )
      P1OUT |= 1 << x;                        // pin goes high

    values[x]++;
    if( values[x] > RESOLUTION )
      values[x] = 0;
  }
  
  for( unsigned x = 0; x < 8; x++ )
  {
    if( values[x+8] == limits[x+8] )
      P2OUT &= ~(1 << x);                     // pin goes low
    else if( values[x+8] == 0 )
      P2OUT |= 1 << x;                        // pin goes high

    values[x+8]++;
    if( values[x+8] > RESOLUTION )
      values[x+8] = 0;
  }
}

void
loop( void )
{
  if( Serial.available() )
  {
    char c = Serial.read();
    if( digit( c ) < 0 )
    {
      in = 0;
      return;
    }
    cmd[in++] = c;
    if( in == 3 )
    {
      in = 0;
      int pin = digit( cmd[0] );
      if( pin >= 0 && pin <= 15 )
      {
        unsigned val = (16 * digit( cmd[1] ) + digit( cmd[2] ) ) % RESOLUTION;
        limits[pin] = val;
      }
      else
      {
        in = 0;
      }
    }
  }
  
  tick();
}

The setup() function sets all 16 pins to be outputs.  Then it calls Serial.begin( 9600 ) to set one of the pins to input, and set the speed of the serial connection to the host computer. Finally, it sets the initial PWM values to an ascending sequence, so the lights get brighter as we move up the pin numbers. That last part is optional -- we could simply let all the LEDs start out dark.

The digit() function simply takes in a character in the set 0123456789ABCDEF, and returns a number from 0 to 15. If the character is not in the set, digit() returns the value -1.

The tick() function is the one that does the Pulse Width Modulation of the pins. For simplicity, we modulate all 16 pins. The Serial class will make certain that we don't interfere with its use of the UART pins.

The tick() function uses two arrays, values and limits. It uses the value array to count how many ticks a pin has been high. It uses the limits array to tell when to set that pin low. The value RESOLUTION determines how many ticks there are before we start a new cycle. If you want to control all 16 LEDs in a program that does not use the serial port, just take out the serial port lines from setup() and loop(), and the program can modulate all 16 pins.

The loop() function checks to see if there is a character waiting to be read. If so, it reads it. If the character is not in our set, we ignore it and return.

The first character says which LED (out of 16, not 14) to change the brightness of. The next two characters tell us how bright to make it. The three characters are sent in hexadecimal, which is both faster and easier for the computer to decode than normal decimal numbers.

If we send the Launchpad the sequence 580 the program will set the LED on P1.5 to half brightness (80 in hexadecimal is 128 decimal, which is halfway to 256, the highest number we can encode in 2 hexadecimal digits). To turn that LED off, we would send the sequence 500. To make it as bright as we can, we send the sequence 5FF. That is actually setting the LED to be on half the time, since our program has a resolution of 512. If you want to allow the LEDs to go to full brightness, you should change RESOLUTION to 256. I use 512 because your eyes can't tell the difference between the brightness levels when they are very bright.

Of course if we can control LED brightness, we can also control motor speed or servo motor position, as we will see in the next chapter.