27 Kasım 2015 Cuma

P10 Led Paneller

LED göstergelerin yaygınlaşması ile P10 denilen 16 satır ve 32 sütundan oluşan hazır LED paneller piyasayı kaplamış durumda. Bu yazımda bu panellerin çalışma mantığını aktarmaya çalışacağım. Daha sonrasında ise elimdeki PIC18F14K50 ile yaptığım basit bir test devresi ve kodu üzerinden pratikte nasıl kontrol edilir bu P10 paneller onun hakkında da bir fikir vermiş olacağım.

Önce bu P10 panellerin devre şemasını inceleyerek başlamak kontrol işleminin anlaşılması bakımından daha iyi olacaktır. Genel olarak devre şeması Şekil 1'de gösterildiği gibidir.
Şekil 1 - P10 Panel Şematiği
Devre bir giriş ve bir çıkış baglantısına sahip böylece arka arkaya istendiği kadar panel (tabii ki bunun bir sınırı var, zira tarama işlemi bunu sınırlamakta) bağlanabilmesine olanak sağlıyor. Giriş kontrol hatları önce 74HC245 serisi bir buffer'a girilmiş, böylece sinyal zayıflamasına karşı bir önlem alınmış. Devrede toplam 16 tane 74HC595 shift register var. Bunlar birbirlerine bağlı. Yani 16 byte shift edildiğinde ilk byte 16 numaralı entegreye gelmiş oluyor. Bunlara 4'er satırın LED'leri bağlanmış durumda. Yani birinci 74HC595'in pinlerine 1 inci satırın ilk 8 LED'i, 2 inci satırın ilk 8 LED'i, ve yine 3 ve 4 üncü satırların ilk 8 LED'i bağlanmış durumda. Aynı şekilde diğer 74HC595'lerde de her pin 4 ardışık satırdaki ve aynı sütundaki LED'ler bağlanmış durumda. Bu 4 satırdaki LED'lerin anotları ise satır olarak ayrılmış ve farklı MOSFET'lere bağlanmış durumda. Yani MOSFET'lerden istenen iletime geçirilerek istenen satırda shift register çıkışınını görünmesi sağlanabilir. İşin burasında devredeki 74HC138 demultiplekser devreye giriyor, zira onun 2 girişi çıkışına bağlı 4 MOSFET'den birini seçmek için kullanılıyor. Bir de devredeki 74HC04 var ki onun da görevi çıkışları aktif eden sinyalin seviyesini evirmek.

Şimdi de kontrol etmekte kullanacağımız giriş bağlantılarının neler olduğuna bir bakalım. Giriş bağlantı soketindeki pinler Şekil 2'deki gibidir.
Şekil 2 - Giriş bağlantı soketi

Burda OE (Output Enable) MOSFET'lerden isteneni seçen 74HC138'in çıkışlarını aktif hale getirmekte kullanılıyor. Bu pine 1 verilirse çıkışlar aktif, 0 verilirse kapalıdır. Böylece istenen bir anda ekran görüntüsü tamamen kapatılabilir.

A ve B pinleri ise 4 MOSFET'den birini, dolayısıyla satır seçimini sağlıyor. Böylece shift register çıkışlarındaki değerler istenen MOSFET'e bağli istenen satır LED'lerinin yanması sağlanmış oluyor.

CKL ve R (isimlendirmeler biraz garip ama orjinal devrede bu şekilde) sırasıyla SPI hattının CLK ve MOSI pinlerinin bağlanacağı pinler. Böylece artık nerdeyse tüm mikrokontrolörlerde bulunan SPI arabirimi kullanılarak tarama işlemi hızlandırılabilir. Hatta artık nerdeyse 8 bit mikrokontrolörlerin yerini almaya başlayan ARM Cortex-Mx ailesi mikrokontrolörlerde standart olarak yer alan DMA ile işlemci zamanını almadan tarama işlemi yapılabilir.

Son kalan SCLK pini ise 74HC595'lere shift edilmiş değerleri çıkışlara aktaran latch pin'i.

PIC18F14K50 İle Örnek Bir Uygulama

Öncelikle hangi pin soketteki hangi pine bağlandı onunla başlayalım. Benim devredeki bağlantılar şu şekilde (P10 panel bağlantı isimlerini daha anlamlı olacak şekilde değiştirdim):

P10(1R)-V706APIC18F14K50
OERC5
ARC4
BRC3
LATCHRC6
SPI_SCLKRB6 (SPI)
SPI_MOSIRC7 (SPI)

Şimdi test kodunu incelemeye başlayabiliriz. Öncelikle panel ile ilgili işlemlerin yapıldığı ledmatrix.c ve ledmatrix.h dosyalarından başlayalım.

#include <xc.h>
#include <string.h>
#include <pic18f14k50.h>
#include "ledmatrix.h"
#include "8x12_horizontal_MSB_1.h"

#define OE                  LATCbits.LC5
#define A                   LATCbits.LC4
#define B                   LATCbits.LC3
#define LATCH               LATCbits.LC6

#define FONT_HEIGHT         12
#define FONT_WIDTH          8

#define ROW_BYTE_COUNT      4
#define COLUMN_BYTE_COUNT   16

unsigned char buffer[2][ROW_BYTE_COUNT][COLUMN_BYTE_COUNT];
unsigned int scan_count = 0;
unsigned char line = 3;
unsigned char active = 0;
unsigned char back = 1;
unsigned char swap = 0;

static void inline LEXMATRIX_SpiSend(unsigned char data)
{
    SSPBUF = data;
    while(!PIR1bits.SSPIF);
    PIR1bits.SSPIF = 0;
}

void interrupt LEDMATRIX_ScanTask(void)
{
    if(TMR0IF)
    {
        unsigned char i = 0;
        unsigned char j = 0;

        for(j = 0; j < ROW_BYTE_COUNT; j++)
        {
            for(i = 0; i < 4; i++)
            {
                LEXMATRIX_SpiSend(buffer[active][j][(i*4) + line]);
            }
        }
        LEDMATRIX_Enable(FALSE);
        switch(line)
        {
            case 3:
                A = B = 0;
                break;

            case 2:
                A = 1;
                break;

            case 1:
                A = 0; B = 1;
                break;

            case 0:
                A = 1;
                break;
        }        
        LATCH = 1;
        LATCH = 0;
        LEDMATRIX_Enable(TRUE);   
        if(line == 0)
        {
            line = 3;
            ++scan_count;
            if(swap)
            {
                swap = back;
                back = active;
                active = swap;
                swap = 0;
                scan_count = 0;
            }
        }
        else --line;
    }
    TMR0IF = 0;
}

void LEDMATRIX_Init(void)
{
    unsigned char i;

    // Used GPIO configuration
    TRISCbits.TRISC5 = 0;    
    OE = 0;
    TRISCbits.TRISC3 = 0;
    TRISCbits.TRISC4 = 0;
    TRISCbits.TRISC6 = 0;
    TRISCbits.TRISC7 = 0;
    TRISBbits.TRISB6 = 0;

    SLRCONbits.SLRC = 0;
    SLRCONbits.SLRB = 0;

    ANSELbits.ANS7 = 0;

    LATCH = 0;
    A = 0;
    B = 1;

    // SPI configuration
    PIR1bits.SSPIF = 0;     // Clear interrupt flag
    PIE1bits.SSPIE = 0;     // Disable MSSP interrupt
    INTCONbits.PEIE = 0;    // Disable peripheral interrupts
    SSPSTATbits.CKE = 0;
    SSPCON1bits.CKP = 0;
    SSPCON1bits.SSPM = 0;
    SSPCON1bits.SSPEN = 1;

    // Timer0 configuration
    T0CONbits.T0CS = 0;     // Transition on instruction clock
    T0CONbits.PSA = 1;      // De-activate pre-scaler
    T0CONbits.T0PS = 0;     // Pre-scaler value 1/2 selected
    INTCON2bits.TMR0IP = 1; // Timer0 interrupt high priority
    INTCONbits.T0IF = 0;    // Clear Timer0 interrupt flag
    INTCONbits.T0IE = 1;    // Enable Timer0 overflow interrupt
    T0CONbits.TMR0ON = 1;   // Enable Timer0

    // Clear the shift register outputs
    for(i = 0; i < 16; i++)
    {
        LEXMATRIX_SpiSend(0xFF);
    }
    LATCH = 1;
    LATCH = 0;
    A = 1;
    B = 0;
    A = 0;
    B = 1;    
     
    PR2 = 0b10010101;       // Timer2 period value 149
    CCP1CON = 0b00001100;   // Set PWM mode        
    CCPR1L = 50;            // Compare low value  
}

void LEDMATRIX_ShowTime(unsigned char hour1, unsigned char hour2, unsigned char min1, unsigned char min2, unsigned char neg)
{
    unsigned char i = 0;
    unsigned char k = 0;

    for(i = 0; i < FONT_HEIGHT; i++)
    {
        if(neg) k = font[hour1 + 48][FONT_HEIGHT - i - 1];
        else    k = ~font[hour1 + 48][FONT_HEIGHT - i - 1];    
        buffer[back][0][i + 1] = (k >> 1) | 0x80;
    }
    for(i = 0; i < FONT_HEIGHT; i++)
    {
        if(neg) k = font[hour2 + 48][FONT_HEIGHT - i - 1];
        else    k = ~font[hour2 + 48][FONT_HEIGHT - i - 1];    
        buffer[back][1][i + 1] = k;
    }
    for(i = 0; i < FONT_HEIGHT; i++)
    {
        if(neg) k = font[min1 + 48][FONT_HEIGHT - i - 1];
        else    k = ~font[min1 + 48][FONT_HEIGHT - i - 1];    
        buffer[back][2][i + 1] = (k >> 2) | 0xC0;
    }
    for(i = 0; i < FONT_HEIGHT; i++)
    {
        if(neg) k = font[min2 + 48][FONT_HEIGHT - i - 1];
        else    k = ~font[min2 + 48][FONT_HEIGHT - i - 1];    
        buffer[back][3][i + 1] = (k >> 1) | 0x80;
    }
    LEDMATRIX_SetPixel(16, 6);
    LEDMATRIX_SetPixel(15, 6);
    LEDMATRIX_SetPixel(16, 7);
    LEDMATRIX_SetPixel(15, 7);
    LEDMATRIX_SetPixel(16, 9);
    LEDMATRIX_SetPixel(15, 9);
    LEDMATRIX_SetPixel(16, 10);
    LEDMATRIX_SetPixel(15, 10);
    swap = 1;
    while(swap);    
}

void LEDMATRIX_ScrollMessage(unsigned char row, unsigned char *msg, unsigned char neg, unsigned int speed)
{
    unsigned char i = 0, j = 0, k = 0;
    unsigned char current_bit_index = FONT_WIDTH - 1;
    unsigned char current_index = 0;
    unsigned char msg_end = 0;

    while(msg_end < (ROW_BYTE_COUNT * 8))
    {
        for(i = 0; i < (ROW_BYTE_COUNT - 1); i++)
        {
            for(j = 0; j < FONT_HEIGHT; j++)
            {
                buffer[back][i][row + j] = (buffer[active][i][row + j] << 1) | (buffer[active][i + 1][row + j] >> 7);
            }
        }
        if(!msg_end)
        {
            for(j = 0; j < FONT_HEIGHT; j++)
            {
                if(neg) k = font[msg[current_index]][FONT_HEIGHT - j - 1];
                else    k = ~font[msg[current_index]][FONT_HEIGHT - j - 1];
                buffer[back][i][row + j] = (buffer[active][i][row + j] << 1) | ((k >> current_bit_index) & 0x01);
            }
            if(current_bit_index == 0)
            {
                current_bit_index = FONT_WIDTH - 1;
                if(++current_index == strlen(msg))
                {
                    msg_end = 1;
                }
            }
            else
            {
                --current_bit_index;
            }
        }
        else
        {
            for(j = 0; j < FONT_HEIGHT; j++)
            {
                if(neg) buffer[back][i][row + j] = (buffer[active][i][row + j] << 1) & 0xFE;
                else    buffer[back][i][row + j] = (buffer[active][i][row + j] << 1) | 0x01;
            }
            ++msg_end;
        }
        swap = 1;
        while(swap);
        while(scan_count != speed);
    }
}

void LEDMATRIX_BufferSet(unsigned char value)
{
    // Set the whole buffer with the provided value
    memset(buffer, value, ROW_BYTE_COUNT * COLUMN_BYTE_COUNT * 2);
}

void LEDMATRIX_Enable(unsigned char enable)
{
    if(enable) 
    {
        T2CON = 0b00000101;
        //OE = 1;
    }
    else
    {
        T2CON = 0b00000001;
        //OE = 0;
    }
}

void LEDMATRIX_Dimming(unsigned char dim)
{
    CCPR1L = dim;
}

void LEDMATRIX_SetPixel(unsigned char x, unsigned char y)
{
    buffer[active][x/8][COLUMN_BYTE_COUNT - 1 - y] = buffer[active][x/8][COLUMN_BYTE_COUNT - 1 - y] & ~(0x80 >> (x % 8));
}

void LEDMATRIX_ClearPixel(unsigned char x, unsigned char y)
{
    buffer[active][x/8][COLUMN_BYTE_COUNT - 1 - y] = buffer[active][x/8][COLUMN_BYTE_COUNT - 1 - y] | (0x80 >> (x % 8));
}

#ifndef __LEDMATRIX_H
#define __LEDMATRIX_H

void LEDMATRIX_Init(void);
void LEDMATRIX_ShowTime(unsigned char hour1, unsigned char hour2, unsigned char min1, unsigned char min2, unsigned char neg);
void LEDMATRIX_ScrollMessage(unsigned char row, unsigned char *msg, unsigned char neg, unsigned int speed);
void LEDMATRIX_BufferSet(unsigned char value);
void LEDMATRIX_Enable(unsigned char enable);
void LEDMATRIX_Dimming(unsigned char dim);
void LEDMATRIX_SetPixel(unsigned char x, unsigned char y);
void LEDMATRIX_ClearPixel(unsigned char x, unsigned char y);

#endif

Bir de main.c'yi verirsek kod tamamlanmış olacak.

#include "config.h"
#include <xc.h>
#include <string.h>
#include <pic18f14k50.h>
#include "ledmatrix.h"

void delay(void)
{
    unsigned long i = 0;
    unsigned long j = 0;

    for(i = 1; i < 1000; i++)
        for(j = 1; j < 1; j++)    
            asm("nop");
}
    
int main(void)
{
    const unsigned char msg[] = "CARSI POZCU"; 
    
    // Initilise all related HW blocks
    LEDMATRIX_Init();
  
    // Clear the buffer
    LEDMATRIX_BufferSet(0xFF);
    
    // Enable the display now
    LEDMATRIX_Enable(TRUE);
    
    // Enable interrupts globally and start matrix scanning
    ei();
    
    while(1)
    {    
        unsigned int h;
        unsigned char a,b,c,d;
        unsigned int dim = 0;
        unsigned char dir = 0;

        LEDMATRIX_BufferSet(0xFF);
        LEDMATRIX_Dimming(25);
        LEDMATRIX_ScrollMessage(1, msg, 0, 150);
        LEDMATRIX_BufferSet(0xFF);
    }
}

Kod kendini açıklıyor aslında o nedenle çalışma mantığına girmek istemiyorum. Değinmek istediğim bir konu var. Bu paneller oldukça ucuza satıldıklarından kullanılan malzeme kalıtesi de oldukça düşük. LED'ler uzun süre yanık kalırsa bozuluyor, lojik entegreler yine bozulabiliyor. Bunun sonucu olarak da piksel hataları veya yanmayan satır/sütun problemleri ortaya çıkıyor. Benim buna bulduğum çözüm ise LED'leri mümkün olduğu kadar sık tarayıp yandığı süre içerisinde fazla ısınmasına müsade etmemek ve sönük kaldığı sürede de yeterince soğumasını sağlamak. Bunu da tarama frekansını arttırarak sağlamaya çalıştım ki işe de yaradı doğrusu.

Son olarak da yukardaki kodun çalışmasına ait görüntüyü eklersem tamam olacak.

2 yorum :

Gültekin Aslan/ Kayatepe İlkokulu dedi ki...

tebrikler. aynı çalışmanın ccs c örnekleri var mı elinizde?

EmbeddedGuru dedi ki...

Bir iki header değişikliği ve kodda bir iki ufak değişiklikle CSS de de derlenebilir yapabilirsiniz.

Yorum Gönder