// Olimex MSP430-413STK Demonstration Program
// Copyright (c) 2002, 2003 Rowley Associates Limited

// This program drives the LCD off the basic timer interrupt.
// Notice that the MSP430-413STK does not use the MSP430's
// built-in LCD controller, but rather drives the LCD directly.

// Timer A drives a simple incrementing count.

#include <msp430x41x.h>

#define _A  (1<<0)   //      AAA
#define _B  (1<<1)   //     F   B
#define _C  (1<<2)   //     F   B
#define _D  (1<<3)   //      GGG
#define _E  (1<<4)   //     E   C
#define _F  (1<<5)   //     E   C
#define _G  (1<<6)   //      DDD
#define _DP (1<<7)

// Map hex digit to logical segment
static const unsigned char stk413_lcd_digit_segments[16] =
{
  _A | _B | _C | _D | _E | _F,       // 0
  _B | _C,                           // 1
  _A | _B | _D | _E | _G,            // 2
  _A | _B | _C | _D | _G,            // 3
  _B | _C | _F | _G,                 // 4
  _A | _F | _G | _C | _D,            // 5
  _A | _F | _G | _C | _D | _E,       // 6
  _F | _A | _B | _C,                 // 7
  _A | _B | _C | _D | _E | _F | _G,  // 8
  _A | _B | _G | _C | _D | _F,       // 9
  _A | _B | _C | _E | _G | _F,       // A
  _C | _D | _E | _F | _F | _G,       // b
  _A | _D | _E | _F,                 // C
  _B | _C | _D | _E | _G,            // d
  _A | _D | _E | _F | _G,            // E
  _A | _E | _F | _G                  // F
};

typedef enum
{
  LCD_2D,
  LCD_2E,
  LCD_2DP,
  LCD_3C,
  LCD_3D,
  LCD_3E,
  LCD_3DP,
  LCD_1,

  LCD_MINUS,
  LCD_COM,
  LCD_BAT,
  LCD_PLUS,   // The : part which needs to be combined with -- (LCD_MINUS)
  LCD_ARROW,
  LCD_AC,
  LCD_3G,
  LCD_3F,

  LCD_3A,
  LCD_3B,
  LCD_2C,
  LCD_1DP,
  LCD_1E,
  LCD_1D,
  LCD_1C,
  LCD_1B,

  LCD_1A,
  LCD_1F,
  LCD_1G,
  LCD_2B,
  LCD_2A,
  LCD_2F,
  LCD_2G,
  LCD_L
} LCDSegment;

unsigned char stk413_lcd_port_shadow[4];

// Maps 8-segment x position to port bit.
static const unsigned char stk413_lcd_digit_map[][8] =
{
  { LCD_1A, LCD_1B, LCD_1C, LCD_1D, LCD_1E, LCD_1F, LCD_1G, LCD_1DP },
  { LCD_2A, LCD_2B, LCD_2C, LCD_2D, LCD_2E, LCD_2F, LCD_2G, LCD_2DP },
  { LCD_3A, LCD_3B, LCD_3C, LCD_3D, LCD_3E, LCD_3F, LCD_3G, LCD_3DP }
};

static void
stk413_lcd_init(void)
{
}

static void
stk413_lcd_set_segment(int segment, int state)
{
  int bit = segment & 7;

  // Disable basic timer interrupts whilst updating.
  IE2 &= ~BTIE;

  if (state)
    stk413_lcd_port_shadow[segment >> 3] |= 1 << bit;
  else
    stk413_lcd_port_shadow[segment >> 3] &= ~(1 << bit);

  // Re-enable interrupts.
  IE2 |= BTIE;
}

// Show digit 'digit' at position 'pos'.
static void
stk413_lcd_show_digit(int digit, int pos)
{
  int i;
  unsigned x = stk413_lcd_digit_segments[digit & 0xf];
  const unsigned char *p = stk413_lcd_digit_map[pos];
  for (i = 0; i < 7; ++i)
    {
      stk413_lcd_set_segment(*p++, x & 1);
      x >>= 1;
    }
}

static void
stk413_lcd_blank_digit(int pos)
{
  int i;
  const unsigned char *p = stk413_lcd_digit_map[pos];
  for (i = 0; i < 7; ++i)
    stk413_lcd_set_segment(*p++, 0);
}

static void
stk413_lcd_show_unsigned(unsigned i)
{
  stk413_lcd_set_segment(LCD_1, i >= 1000);
  if (i >= 100)
    stk413_lcd_show_digit(i / 100 % 10, 2);
  else
    stk413_lcd_blank_digit(2);
    
  if (i >= 10)
    stk413_lcd_show_digit(i / 10 % 10, 1);
  else
    stk413_lcd_blank_digit(1);

  stk413_lcd_show_digit(i % 10, 0);
}

// Basic timer interrupt refreshes the LCD
static void
basic_timer_interrupt(void) __interrupt[BASICTIMER_VECTOR]
{
  static int x = 0;
  static unsigned u = 7;
  x = ~x;

  P3OUT = stk413_lcd_port_shadow[0] ^ x;
  P4OUT = stk413_lcd_port_shadow[1] ^ x;
  P5OUT = stk413_lcd_port_shadow[2] ^ x;
  P6OUT = stk413_lcd_port_shadow[3] ^ x;
}

// Increment the number on display.
static void
timera_interrupt(void) __interrupt[TIMERA0_VECTOR]
{
  static unsigned u;
  stk413_lcd_show_unsigned(++u);
  if (u == 2000)
    u = 0;
  // Interrupt again in 50000 cycles.
  CCR0 += 50000;
}
  
void
main(void)
{
  int i;

  // Turn off the watchdog.
  WDTCTL = WDTHOLD + WDTPW;

  // Configure FLL.
  FLL_CTL0 = XCAP10PF;
  FLL_CTL1 = FLL_DIV_4;
  SCFQCTL = 74;  // fDCO=75*32768=2457600 Hz
  for (i = 0; i < 0x8000; ++i)
    ;

  // Set up interrupts.
  IE1 = 0;
  IE2 = 0;
  IFG1 = 0;
  IFG2 = 0;
  
  // Configure Timer A.
  TACTL = TASSEL1 + TACLR;              // SMCLK, clear TAR
  CCTL0 = CCIE;                         // CCR0 interrupt enabled
  CCR0 = 50000;
  TACTL |= MC1;                         // Start Timer_A in continuous mode

  // Set up ports  
  P1DIR = 0x01;
  P2DIR = 0xc0;
  P3DIR = 0xff;
  P4DIR = 0xff;
  P5DIR = 0xff;
  P6DIR = 0xff;

  // Start things up  
  stk413_lcd_init();

  // Turn on Basic Timer interrupts
  BTCTL = 0x0f;
  IE2 |= BTIE;

  // Globally enable interrupts.
  _EINT();

  // If we ever come out of LPM0, go back to it.
  for (;;)
    LPM0;
}

