이런거 저런거 그런거

4개 LED Matrix (8x32) 컨트롤 하기 본문

이것저것 해보기/아두이노 가지고 놀기

4개 LED Matrix (8x32) 컨트롤 하기

빵진 2019. 10. 6. 20:30

8x8 한개에 더 나아가 동일한 Matrix가 4개 붙어있는 부품을 샀다.

모델명 : SZH-EKAD-115 (MAX7219 아두이노 8X32 도트 매트릭스 모듈)

 

이 부품 역시 SPI 통신을 통해 컨트롤 할 수 있으며 Daisy-Chain방식을 통해 핀을 더 할당하지 않고도 4개의 MAX7219를 컨트롤 하여 4개의 Matrix Led를 컨트롤 할 수 있다.

 

그렇다면 궁금한 부분은

[아두이노] --- SPI ---> [Matrix 3] --- SPI ---> [Matrix 2] --- SPI ---> [Matrix 1] --- SPI ---> [Matrix 0]

 

위와 같이 연결되어있다고 했을 때 [Matrix 0]의 Led만 컨트롤 하고 싶을 경우 데이터를 어떻게 보내야 하는가 이다.

이와 관련하여 이해하기 쉬운 동영상 설명을 찾았다.

https://www.youtube.com/watch?v=SMH45vnRbjI

MAX7219 Daisy-Chain

즉, SPI로 연결된 MAX7219 갯수만큼 데이터를 보내야 한다.

보내기 전, 후로 CS핀을 컨트롤 하며 CS핀을 High로 컨트롤 하는 순간 보낸 데이터가 적용된다.

 

보내는 데이터의 순서는 MAX7219갯수 x 2 byte(address + data)를 보내야 하며 각 MAX7219는 2byte 큐와 같다고 생각하면 된다. 따라서 예를 들면 

[아두이노] --- SPI ---> [Matrix 3] --- SPI ---> [Matrix 2] --- SPI ---> [Matrix 1] --- SPI ---> [Matrix 0]

[Matrix 1] 를 컨트롤 하고 싶다면 아래와 같이 하면 된다는 것이다.

Control_Matrix_1(Addr, Value) {

    CS-Pin LOW

    Send No-Op, 0x00  // For Matrix 0

    Send Addr, Value  // For Matrix 1

    Send No-Op, 0x00  // For Matrix 2

    Send No-Op, 0x00  // For Matrix 3

    CS-Pin HIGH

}

여기서 No-Op란 Address 0x00에 해당한다. 

(보내는 Signal은 Address + Data, 총 2 byte여야 하므로 No-Op를 보낸 후 1byte의 데이터(아무거나)를 보내준다)

 

 

추후 조도, 온도, 습도를 표시하는데도 사용하기 위해 

4개의 Matrix Led를 컨트롤 하기 위해 Class로 만들어서 좀 보기 좋게(?) 작성하기로 했다.

 

이번 역시 Library를 사용하지 않는 방향으로...

아래는 4개의 Matrix Led에 숫자를 차례대로 증가시키면서 출력하는 샘플코드다.

(추가로 밝기도 0 ~ 0xF 까지 차례대로 증가하도록 설정한다.)

 

/*
 * MAX7219 Control Register Address&Default Value
 */
#define NO_OP_ADDR        (0x00)
#define NO_OP_VAL       (0x00)
#define DECODE_MODE_ADDR    (0x09)
#define DECODE_MODE_VAL     (0x00)
#define INTENSITY_ADDR      (0x0A) //Bright Setting (0x00 ~ 0x0F)
#define INTENSITY_VAL     (0x00)
#define SCAN_LIMIT_ADDR     (0x0B) //How many use digit led (line count in matrix led device)
#define SCAN_LIMIT_VAL      (0x07)
#define POWER_DOWN_MODE_ADDR  (0x0C)
#define POWER_DOWN_MODE_VAR   (0x01)
#define TEST_DISPLAY_ADDR   (0x0F)
#define TEST_DISPLAY_VAL    (0x00)

class MultiMatrix {
  /*
   * Definitions
   */
  public:
    static const int MODULE_COUNT = 4;
    enum Flip {
      FLIP_NONE = 0,
      FLIP_H,
      FLIP_V,
      FLIP_HV,
      FLIP_MAX,
    };
    
  private:
    /* LED Pattern */
    const char NUMBER[10][3] = {
      {0x1f, 0x11, 0x1f}, //0
      {0x00, 0x00, 0x1f}, //1
      {0x1d, 0x15, 0x17}, //2
      {0x15, 0x15, 0x1f}, //3
      {0x07, 0x04, 0x1f}, //4
      {0x17, 0x15, 0x1d}, //5
      {0x1f, 0x15, 0x1d}, //6
      {0x03, 0x01, 0x1f}, //7
      {0x1f, 0x15, 0x1f}, //8
      {0x17, 0x15, 0x1f}  //9
    };
    const int NUMBER_AREA_MASK = 0x1F;

  /*
   * Member Values
   */
  private:
    int clk, cs, din;
    enum Flip flip;
    unsigned char display_buffer[MODULE_COUNT][8];

  /*
   * Member Functions
   */
  public:
    MultiMatrix(void);
    ~MultiMatrix() {};

    int init(enum Flip flip, int clkPin, int csPin, int dinPin);
    int draw(int idx, int row, int col, int state);
    int draw_number(int idx, int number);
    int set_bright(unsigned char bright);
    int set_bright(int idx, unsigned char bright);
    void update(void);

  private:
    void send_data(int idx, uint8_t addr, uint8_t value);
    void send_same_data(uint8_t addr, uint8_t value);
};

MultiMatrix::MultiMatrix(void)
{
  int i;
  for (i = 0; i < MODULE_COUNT; i++) {
    memset(this->display_buffer[i], 0x00, 8);
  }
}

void MultiMatrix::send_data(int idx, uint8_t addr, uint8_t value)
{
  int i;

  digitalWrite(this->cs, LOW);
  for (i = 0; i < MODULE_COUNT; i++) {
    if (i == idx) {
      //send Data
      shiftOut(this->din, this->clk, MSBFIRST, addr);
      shiftOut(this->din, this->clk, MSBFIRST, value);
    } else {
      //send No-Op
      shiftOut(this->din, this->clk, MSBFIRST, NO_OP_ADDR);
      shiftOut(this->din, this->clk, MSBFIRST, NO_OP_VAL);
    }
  }
  digitalWrite(this->cs, HIGH);
}

void MultiMatrix::send_same_data(uint8_t addr, uint8_t value)
{
  int i;
  digitalWrite(this->cs, LOW);
  for (i = 0; i < MODULE_COUNT; i++) {
    //send Data
    shiftOut(this->din, this->clk, MSBFIRST, addr);
    shiftOut(this->din, this->clk, MSBFIRST, value);
  }
  digitalWrite(this->cs, HIGH);
}

int MultiMatrix::init(enum Flip flip, int clkPin, int csPin, int dinPin)
{
  int i;

  this->flip = flip;

  //set pins
  this->clk = clkPin;
  this->cs = csPin;
  this->din = dinPin;
  pinMode(clkPin, OUTPUT);
  pinMode(csPin, OUTPUT);
  pinMode(dinPin, OUTPUT);

  //setting modules to initial value
  for (i = 0; i < MODULE_COUNT; i++) {
    send_data(i, DECODE_MODE_ADDR, DECODE_MODE_VAL);
    send_data(i, INTENSITY_ADDR, INTENSITY_VAL);
    send_data(i, SCAN_LIMIT_ADDR, SCAN_LIMIT_VAL);
    send_data(i, POWER_DOWN_MODE_ADDR, POWER_DOWN_MODE_VAR);
    send_data(i, TEST_DISPLAY_ADDR, TEST_DISPLAY_VAL);
  }

  return 0;
}


int MultiMatrix::draw(int idx, int row, int col, int onOff)
{
  if (idx >= MODULE_COUNT)
    return -1;
  if (row < 0 || 7 < row)
    return -1;
  if (col < 0 || 7 < col)
    return -1;
  if (onOff)
    this->display_buffer[idx][col] |= ((0x1) << row);
  else
    this->display_buffer[idx][col] &= ~((0x1) << row);
}

int MultiMatrix::draw_number(int idx, int number)
{
  int i;
  unsigned char data[8] = {0,};

  if (idx >= MODULE_COUNT)
    return -1;
  if (number <  0 || 100 < number)
    return -1;

  /*
   * Clear Buffer
   */
  for (i = 0; i < 8; i++) {
    this->display_buffer[idx][i] &= ~(NUMBER_AREA_MASK);
  }

  /*
   * Draw
   */
  this->display_buffer[idx][1] |= NUMBER[number / 10][0];
  this->display_buffer[idx][2] |= NUMBER[number / 10][1];
  this->display_buffer[idx][3] |= NUMBER[number / 10][2];
  this->display_buffer[idx][5] |= NUMBER[number % 10][0];
  this->display_buffer[idx][6] |= NUMBER[number % 10][1];
  this->display_buffer[idx][7] |= NUMBER[number % 10][2];

  return 0;
}

int MultiMatrix::set_bright(unsigned char bright)
{
  int i;
  
  if (bright < 0x0 || 0xF < bright)
    return -1;

  send_same_data(INTENSITY_ADDR, (uint8_t)bright);
  return 0;
}

int MultiMatrix::set_bright(int idx, unsigned char bright)
{
  if (bright < 0x0 || 0xF < bright)
    return -1;
  send_data(idx, INTENSITY_ADDR, (uint8_t)bright);
  return 0;
}

void MultiMatrix::update(void)
{
  int i, j;

  for (i = 0; i < 8; i++) {
    digitalWrite(this->cs, LOW);
    for (j = 0; j < MODULE_COUNT; j++) {
      //- send address
      shiftOut(this->din, this->clk, MSBFIRST, i + 1);
      //- send data
      switch (this->flip) {
      case FLIP_NONE:
        shiftOut(this->din, this->clk, MSBFIRST, this->display_buffer[j][i]);
        break;
      case FLIP_H:
        shiftOut(this->din, this->clk, MSBFIRST, this->display_buffer[j][7 - i]);
        break;
      case FLIP_V:
        shiftOut(this->din, this->clk, LSBFIRST, this->display_buffer[j][i]);
        break;
      case FLIP_HV:
        shiftOut(this->din, this->clk, LSBFIRST, this->display_buffer[j][7 - i]);
        break;
      }
    }
    digitalWrite(this->cs, HIGH);
  }
}



/*
 * Application Codes
 */
#define MAX7219_CLK (10)
#define MAX7219_CS  (11)
#define MAX7219_DIN (12)

MultiMatrix led;

void setup() {
  led.init(MultiMatrix::FLIP_HV, MAX7219_CLK, MAX7219_CS, MAX7219_DIN);
}

void loop() {
  static int i;
  if (i > 100) {
    i = 0;
  }
  led.draw_number((i % 4), i);
  led.set_bright((i % 4), i % 0xF);
  led.update();
  i++;
  
  delay(500);
}

 

뭔가 이번엔 길다.....

위 코드 중 일부인 MultiMatrix Class의 멤버함수만 설명해보자면,

  public:
    MultiMatrix(void);
    ~MultiMatrix() {};

    int init(enum Flip flip, int clkPin, int csPin, int dinPin);
    int draw(int idx, int row, int col, int state);
    int draw_number(int idx, int number);
    int set_bright(unsigned char bright);
    int set_bright(int idx, unsigned char bright);
    void update(void);

  private:
    void send_data(int idx, uint8_t addr, uint8_t value);
    void send_same_data(uint8_t addr, uint8_t value);
  • init()
    • flip : 출력 방향을 설정한다. 8x8기준으로 8x32 자재는 Matrix Led 모듈이 거꾸로 달려있어 HV Flip 을 해 줘야 한다. H Flip의 경우 buffer array를 거꾸로 읽는 방법으로 지원했으며, V Flip의 경우 기존 MSBFIRST를 LSBFIRST로 변경하여 간단히 해결하였다.(결국 둘 다 어디서부터 읽어 갈거냐에 대한 설정)
      즉, 위 두 개를 적용하면 글자를 360도 거꾸로 돌릴 수 있는 것이다.
    • clkPin, csPin, dinPin : SPI통신을 위한 3개의 핀
  • draw() : Led OnOff를 idx에 해당하는 Matrix Led의 버퍼에 설정한다. (아두이노에서 제일 먼 Matrix Led가 0번에 해당한다.)
  • draw_number() : 숫자를 해당 idx에 해당하는 Matrix Led의 버퍼에 그린다. 00 ~ 99 까지 지원하며 그리는 위치는 Row 0 ~ 4로 총 5개 Row을 사용한다.
  • set_bright() : 전체 Led에 대한 밝기 또는 특정 Matrix에 대한 밝기 설정 (밝기 설정 범위 : 0x0 ~ 0xF)
  • update() : 화면 업데이트 (draw()나 draw_number() 호출만으로 Led가 업데이트 되지 않으며 update()함수가 불러야  실제로 Led가 컨트롤 된다.)
  • send_data() : 특정 matrix led에 데이터를 보낸다.
  • send_same_data() : 전체 matrix led에 대해 동일한 데이터를 보낸다. (ex> 밝기 전체 설정)

 

위 설명과 같이 클래스 내부에는 display buffer가 존재하며 draw(), draw_number()는 버퍼를 대상으로만 작업한다.

그리고 설정한 내용이 Led에 표시되려면 update()를 호출해야한다.

 

MultiMatrix의 display_buffer 변수와 실제 Led Matrix를 매칭 시켜보면 아래와 같다.

 

 

 

 

 

Comments