18 Aralık 2015 Cuma

PIC ve EM4102 ile RFID Okuma Devresi

RFID ik çıktığı yıllarda Üniversitedeydim ve bunların birçok alanda kullanılacağı söyleniyordu. Aradan fazla zaman geçmedi ve binaların giriş kapılarının otomatik açılması işlerine kadar girdiler. Şu anda 125KHz ve 13.56MHz olmak üzere iki gruba ayrılmış durumda RFID ürünleri. 125KHz olanı tranceiver kullanmadan basit devrelerle ve yazılımla okumak ve yazmak mümkün ve internette bu konuda birçok örnek uygulama mevcut. Genelde daha yüksek hız ve güvenilirlik istenen uygulamalarda ise 13.56MHz de çalışan RFID'ler tercih ediliyor.  Ben de elimdeki PIC16F690 geliştime kartını denemek amaçlı elimdeki EM4102 (RFID tranceiver diyebiliriz) ile bir deneme yaptım. Sonucunu aşağıdaki video dan görebilirsiniz. EM4102'nin çevre elemanlarının boyulandırılmasına ve PCB yerleşimine dikkat etmek gerekiyor. Zira uygulamaların genel karakteristiği icabı 7/24 çalışacak bir entegre ve kötü tasarım durumda ısınmaya çok elverişli ki buda entegre ömrünü azaltacaktır. Bir mühendis olarak hiç tahammül edemediğim şey kötü tasarım sonucu kısa ömürlü olan ürünlerdir. Genelde Çin menşeli ürünlerde söz konusu olan bu bir kere çalışsın yeter mantığı markalaşma bilincini yerleşmemiş olmasından kaynaklanıyor büyük ölçüde. Marka imajı derdinde olan bir şirketin pek kolay kolay tevessül etmeyeceği yollar bunlar ama dediğim gibi markalaşma bilincinin eksik olduğu bizim gibi ülkelerde çok olağan durumlar.  


Zilog Z8F6423 ve Wiznet W5100 ile uzaktan kontrol

İlk kullandığım mikrokontrolör Z8 olmuştur. Üniversite yıllarımda o zamanın parasıyla oldukça ucuz olan bir geliştirme kitlerini edinmiştim. Ayrıca ücretsiz örnek de gönderiyorlardı. Hepsinden önemlisi zamanına göre oldukça basit HW debugger'ı vardı. Seri port üzerinden ve tek pin üzerinden debug ve kod yükleme yapılabiliyordu. Debugger devresi bir MAX232 türevinden ibaretti. PIC'ciler için hayaldi bu özellikler. Onlar kodu yazar, derler ve yükler daha sonra çalışmasına bakarak ilerlerken ben Z8 ile kodu yazar derler board üzerinde debug eder, breakpoint falan kullanırdım. Dahası ücrestiz C derleyicisi ve kendi IDE'si olan başka üretici de bilmiyorum o zamanlar. Tabii daha sonrasında Zilog ürünleri geliştirmede yavaş kalınca şirket eridi gitti. Özellikle güç tüketimi konusunda birşey yapamadılar. Z8 ailesinin çok ilgin bir özelliği vardı ki o özellik günümüzde bile başka mikrokontrolörde yok. PIC'lerdeki gibi bir W (working) register'a ihtiyacı yoktu çünkü dahili tüm sram alanı W gibi kullanılabiliyordu. Ayrıca DMA'sı var. Neyse lafı uzatmaya gerek yök ölü bir ürün için.

O zamanlar Wiznet diye Kore'li bir şirket çıkmış ve W5100 diye HW TCP/IP desteği sunan bir çip üreticisi çıkmıştı. Bu W5100 aynı zamanda PHY da barındırıyordu içerisinde ve sadece trafolu bir ETH konnektörü bağlamak yetiyordu en küçük mikrokontrolöre bile. 4 taneye kadar HW socket'e izin veriyordu ve dahili buffer alanı da vardı. Elime bu W5100 geliştime kitinden geçmişti ve Z8 ile uzaktan konrol uygulaması yapıp denemek istedim. Sonuç aşağdaki video da gördüğünüz gibi bir devre oldu. Dahili web server üzerinden 4 röle kontrol edilebiliyor. Sisteme giriş şifre korumalı.


29 Kasım 2015 Pazar

Farklı kernel versiyonu ile derlenmiş modülü yükleme

Embedded veya değil, linux ile uğraşıyorsanız bazen farklı bir kernel versiyonun da derlenmiş kernel modüllerini yüklemeniz gerekebilir.  Fakat bu durumda “insmod -f “ ile zorlasanız dahi modül yüklenmez. Bu sorunu aşmanın kolay bir yolu var. Bunun için .ko dosyasınaki .modinfo kısmını yeniden adlandırmanız yetecektir. Bu durumda kernel versiyon tanımını bulamayacağı için yükleme sırasında aşağıdakine benzer bir hata verecektir ama devam da edecektir.

“<modül adı>: no version magic, tainting kernel.”

Böylece modülü yüklemiş olacaksınız.

Son olarakta bu işlemi nasıl yapacağınıza gelince, aslında bunu objcopy ile kolayca yapabilirsiniz, şöyle ki:

objcopy <modül adı>.ko –rename-section .modinfo=.rastgelebirsey

Genelde cross derleyicilerde objcopy’nin önüne işlemciyi belirten bir ön ek de gelir. Umarım işinize yarar.

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.