#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/io.h>
  //PORTD
#define A_High PORTD |= 0x04  // A
#define A_Low PORTD &= ~0x04  // Arduino Uno 2번핀
#define B_High PORTD |= 0x08  // B
#define B_Low PORTD &= ~0x08  // Arduino Uno 3번핀
#define C_High PORTD |= 0x10  // C
#define C_Low PORTD &= ~0x10  // Arduino Uno 4번핀
#define D_High PORTD |= 0x20  // D
#define D_Low PORTD &= ~0x20  // Arduino Uno 5번핀
#define R1_ON PORTD |= 0x40 // Red1
#define R1_OFF PORTD &= ~0x40 // Arduino Uno 6번핀
#define G1_ON PORTD |= 0x80 // Green1
#define G1_OFF PORTD &= ~0x80 // Arduino Uno 7번핀
#define B1_ON PORTB |= 0x01 // Blue1
#define B1_OFF PORTB &= ~0x01 // Arduino Uno 8번핀
// PORTB
#define R2_ON PORTB |= 0x02 // Red2
#define R2_OFF PORTB &= ~0x02 // Arduino Uno 9번핀
#define G2_ON PORTB |= 0x04 // Green2
#define G2_OFF PORTB &= ~0x04 // Arduino Uno 10번핀
#define B2_ON PORTB |= 0x08 // Blue2
#define B2_OFF PORTB &= ~0x08 // Arduino Uno 11번핀
#define Clk_enable PORTB |= 0x10  // Clk
#define Clk_disable PORTB &= ~0x10  // Arduino Uno 12번핀
#define OE_enable PORTB &= ~0x20  // Output_Enable(OE)
#define OE_disable PORTB |= 0x20  // Arduino Uno 13번핀
// PORTC
#define LE_enable PORTC |= 0x08 // Latch_Enable(LE)
#define LE_disable PORTC &= ~0x08 // Arduino Uno A3번핀 
volatile unsigned char BCD_Num = 0; // 도트매트릭스 표시할 문자 색상 결정
unsigned char Dot_char_cnt = 0; // 도트매트릭스에 표시될 문자 카운팅
unsigned char move_motion = 0;  // 도트매트릭스의 디스플레이 모션 방식 설정
unsigned int Move_cnt = 32; // 도트매트릭스 왼쪽, 오른쪽 시프트 변수
unsigned int Move_cnt2 = 0; // 도트매트릭스 왼쪽, 오른쪽 시프트 변수2 
unsigned int str_speed_cnt = 0;
bool flag_Oe = 0;
ISR(TIMER2_OVF_vect)
{
  // 0.1mS 오버플로우 인터럽트 발생
  static unsigned int string_moving_speed_value = 500;  // 도트매트릭스 시프트 속도
  static unsigned int cnt = 0;
  if (cnt == 500)
  {
    LE_enable;
  }
  else if (cnt >= 1000)
  {
    LE_disable;
    cnt = 0;
  }

  str_speed_cnt++;
  if (str_speed_cnt > string_moving_speed_value)  // 문자가 50mS 머문 후 시프트 시작
  {
    str_speed_cnt = 0;
    Move_cnt2++;
    if (Move_cnt2 == 32)
    {
      Move_cnt2 = 0;
      BCD_Num++;
      if (BCD_Num > 7)
      {
        BCD_Num = 1;  // 출력 문자 색상 설정,BCD_NUM 값에 따라 출력 문자 색상 변경 가능 = 0;
      }
    }
  }

  TCNT2 = 0xe7;
}

void setup()
{
  DDRB = 0xff;  // PORTB 출력 방향 설정
  DDRC = 0xff;  // PORTC 출력 방향 설정
  DDRD = 0xff;  // PORTD 출력 방향 설정
  TCCR2A = 0x00;
  TCCR2B = 0x04;  // 타이머 카운터2 노멀 모드, 64분주 
  TIMSK2 = 0x01;  // 타이머 카운터2 인터럽트 허용
  TCNT2 = 0xe7; // 타이머 카운터2 초기값: 231
  SREG = 0x80;  // 인터럽트 허용
}

void row_dynamic(unsigned char i)
{
  if(i<16)
  {
    switch (i)  // 행 이동
    {
      case 0:
        A_Low;B_Low;C_Low;D_Low;break;  // 1행 LED 
      case 1:
        A_High;B_Low;C_Low;D_Low;break;  // 2행 LED 
      case 2:
        A_Low;B_High;C_Low;D_Low;break;  // 3행 LED 
      case 3:
        A_High;B_High;C_Low;D_Low;break;  // 4행 LED 
      case 4:
        A_Low;B_Low;C_High;D_Low;break;  // 5행 LED 
      case 5:
        A_High;B_Low;C_High;D_Low;break;  // 6행 LED 
      case 6:
        A_Low;B_High;C_High;D_Low;break;  // 7행 LED 
      case 7:
        A_High;B_High;C_High;D_Low;break;  // 8행 LED
      case 8:
        A_Low;B_Low;C_Low;D_High;break;  // 9행 LED
      case 9:
        A_High;B_Low;C_Low;D_High;break;  // 10행 LED
      case 10:
        A_Low;B_High;C_Low;D_High;break;  // 11행 LED
      case 11:
        A_High;B_High;C_Low;D_High;break;  // 12행 LED
      case 12:
        A_Low;B_Low;C_High;D_High;break;  // 13행 LED
      case 13:
        A_High;B_Low;C_High;D_High;break;  // 14행 LED
      case 14:
        A_Low;B_High;C_High;D_High;
        break;  // 15행 LED
      case 15:
        A_High;B_High;C_High;D_High;break;
      default:
        A_Low;B_Low;C_Low;D_Low;break; 
    }
  }
  else   {
    switch (i-16)  // 행 이동
    {
      case 0:
        A_Low;B_Low;C_Low;D_Low;break;  // 1행 LED 
      case 1:
        A_High;B_Low;C_Low;D_Low;break;  // 2행 LED 
      case 2:
        A_Low;B_High;C_Low;D_Low;break;  // 3행 LED 
      case 3:
        A_High;B_High;C_Low;D_Low;break;  // 4행 LED 
      case 4:
        A_Low;B_Low;C_High;D_Low;break;  // 5행 LED 
      case 5:
        A_High;B_Low;C_High;D_Low;break;  // 6행 LED 
      case 6:
        A_Low;B_High;C_High;D_Low;break;  // 7행 LED 
      case 7:
        A_High;B_High;C_High;D_Low;break;  // 8행 LED
      case 8:
        A_Low;B_Low;C_Low;D_High;break;  // 9행 LED
      case 9:
        A_High;B_Low;C_Low;D_High;break;  // 10행 LED
      case 10:
        A_Low;B_High;C_Low;D_High;break;  // 11행 LED
      case 11:
        A_High;B_High;C_Low;D_High;break;  // 12행 LED
      case 12:
        A_Low;B_Low;C_High;D_High;break;  // 13행 LED
      case 13:
        A_High;B_Low;C_High;D_High;break;  // 14행 LED
      case 14:
        A_Low;B_High;C_High;D_High;
        break;  // 15행 LED
      case 15:
        A_High;B_High;C_High;D_High;break;
      default:
        A_Low;B_Low;C_Low;D_Low;break; 
    }
  }
}

void shift_Register2(unsigned char high, unsigned char low, unsigned char cnt_row)
{
  // 점등시키고자 하는 LED 16bit를 high(8bit), low(8bit)로 나누어
  // cnt_row 인자값에 따라 상위 8비트, 하위 8비트 출력
  static unsigned char clk = 0;
  static unsigned int buff = 0;
  buff = high << 8; // 상위 8bit 좌측으로 8번 시프트 
  buff |= low;  // 상위 8bit, 하위 8bit 결합
  for (clk = 0; clk < 16; clk++)
  {
    // 16비트 데이터를 1비트씩 시프트레지스터에 입력
    if (buff &(1 << clk))
    {
      switch (BCD_Num)
      {
        case 0:
          R1_OFF;
          G1_OFF;
          B1_OFF;
          R2_OFF;
          G2_OFF;
          B2_OFF;
          break;
        case 1: // Red  
          if (cnt_row < 16)
          {
            // 상위 8비트 디스플레이 ON, 하위 8비트 디스플레이 OFF 
            R1_ON;
            G1_OFF;
            B1_OFF;
            R2_OFF;
            B2_OFF;
            G2_OFF;
          }
          else
          {
            // 하위 8비트 디스플레이 ON, 상위 8비트 디스플레이 OFF
            R2_ON;
            G2_OFF;
            B2_OFF;
            R1_OFF;
            B1_OFF;
            G1_OFF;
          }

          break;
        case 2: // Green
          if (cnt_row <= 15)
          {
            // 상위 8비트 디스플레이 ON, 하위 8비트 디스플레이 OFF
            R1_OFF;
            G1_ON;
            B1_OFF;
            R2_OFF;
            B2_OFF;
            G2_OFF;
          }
          else
          {
            // 하위 8비트 디스플레이 ON, 상위 8비트 디스플레이 OFF
            R2_OFF;
            G2_ON;
            B2_OFF;
            R1_OFF;
            B1_OFF;
            G1_OFF;
          }

          break;
        case 3: // Blue
          if (cnt_row < 16)
          {
            // 상위 8비트 디스플레이 ON, 하위 8비트 디스플레이 OFF 
            R1_OFF;
            G1_OFF;
            B1_ON;
            R2_OFF;
            B2_OFF;
            G2_OFF;
          }
          else
          {
            // 하위 8비트 디스플레이 ON, 상위 8비트 디스플레이 OFF
            R2_OFF;
            G2_OFF;
            B2_ON;
            R1_OFF;
            B1_OFF;
            G1_OFF;
          }

          break;

        case 4: // Pink
          if (cnt_row < 16)
          {
            // 상위 8비트 디스플레이 ON, 하위 8비트 디스플레이 OFF 
            R1_ON;
            B1_ON;
            G1_OFF;
            R2_OFF;
            B2_OFF;
            G2_OFF;
          }
          else
          {
            // 하위 8비트 디스플레이 ON, 상위 8비트 디스플레이 OFF
            R2_ON;
            B2_ON;
            G2_OFF;
            R1_OFF;
            B1_OFF;
            G1_OFF;
          }

          break;

        case 5: // Yellow  
          if (cnt_row < 16)
          {
            // 상위 8비트 디스플레이 ON, 하위 8비트 디스플레이 OFF 
            R1_ON;
            G1_ON;
            B1_OFF;
            R2_OFF;
            B2_OFF;
            G2_OFF;
          }
          else
          {
            // 하위 8비트 디스플레이 ON, 상위 8비트 디스플레이 OFF
            R2_ON;
            G2_ON;
            B2_OFF;
            R1_OFF;
            B1_OFF;
            G1_OFF;
          }

          break;
        case 6: // Sky  
          if (cnt_row < 16)
          {
            // 상위 8비트 디스플레이 ON, 하위 8비트 디스플레이 OFF 
            R1_OFF;
            G1_ON;
            B1_ON;
            R2_OFF;
            B2_OFF;
            G2_OFF;
          }
          else
          {
            // 하위 8비트 디스플레이 ON, 상위 8비트 디스플레이 OFF
            R2_OFF;
            G2_ON;
            B2_ON;
            R1_OFF;
            B1_OFF;
            G1_OFF;
          }

          break;
        case 7: // White  
          if (cnt_row < 16)
          {
            // 상위 8비트 디스플레이 ON, 하위 8비트 디스플레이 OFF 
            R1_ON;
            G1_ON;
            B1_ON;
            R2_OFF;
            B2_OFF;
            G2_OFF;
          }
          else
          {
            // 하위 8비트 디스플레이 ON, 상위 8비트 디스플레이 OFF
            R2_ON;
            G2_ON;
            B2_ON;
            R1_OFF;
            B1_OFF;
            G1_OFF;
          }
          break;
        default:
          R1_OFF;
          G1_OFF;
          B1_OFF;
          // 상위 8비트 디스플레이 OFF, 하위 8비트 디스플레이 OFF 
          R2_OFF;
          G2_OFF;
          B2_OFF;
          break;
      }
    }
    else
    {
      R1_OFF;
      G1_OFF;
      B1_OFF;
      R2_OFF;
      G2_OFF;
      B2_OFF;
    }

    Clk_enable; // CLK 신호 활성화
    Clk_disable;
  }
}

void ActivePulse()  // 스트로브 신호
{
  LE_enable;
  LE_disable; // LE 신호 활성화   
  OE_enable;  // OE 신호 활성화
  delayMicroseconds(400); // 400uS 딜레이 
  OE_disable;
}

void dot1_display() // 도트매트릭스 문자 출력 함수
{
  static unsigned int i_cnt = 0;
  unsigned int data_shift_high = { 0 }; // 도트매트릭스1 상위 16비트 변수 저장
  unsigned int data_shift_low = { 0 };  // 도트매트릭스2 하위 16비트 변수 저장
  unsigned int buff1[16] = { 0 }; // 도트매트릭스1 출력 문자 저장
  unsigned int buff2[16] = { 0 }; // 도트매트릭스2 출력 문자 저장
  unsigned char high1 = 0;  // 도트매트릭스1 상위 bit 저장 변수 
  unsigned char low1 = 0; // 도트매트릭스1 하위 bit 저장 변수
  unsigned char high2 = 0;  // 도트매트릭스2 상위 bit 저장 변수
  unsigned char low2 = 0; // 도트매트릭스2 하위 bit 저장 변수  
  register unsigned int i = 0;
  
  data_shift_high = 0x0000;
  data_shift_low = 0x0001;
  for (i = 0; i < 32; i++)
  {
    buff1[i] = data_shift_low;
    buff2[i] = data_shift_high;
    
    high1 = (buff1[i] >> 8);  //  상위 8Bit 31~23  저장
    low1 = (buff1[i] &0xff);  //  하위 8Bit 22~16 저장
    
    high2 = (buff2[i] >> 8);  // 상위 8Bit 15~8 저장
    low2 = (buff2[i] &0xff);  // 하위 8Bit 7~0 저장
    row_dynamic(i); // 0~15행 접근
    shift_Register2(high1, low1, i);  // 상위 16bit LED 구동 클럭
    shift_Register2(high2, low2, i);  // 하위 16bit LED 구동 클럭
    ActivePulse();  // 스트로브 신호
  }
}

void loop()
{
  dot1_display();
}
