Yeraltı dünyasının yazılmamış bir kuralı vardır: En iyi sır, en basit görünen detayın içinde saklıdır. Yıllardır bana “Bunu nasıl yaptın?” diye sordular. Forumlarda, özel mesajlarda, hatta o meşhur Bozyaka’nın nemli duvarları arasında bile fısıltıyla adım geçti. Cevap, aslında her zaman gözünüzün önündeydi: Manyetik şeridin o anlık, o kısacık dansında.
Bugün, o dansın koreografisini, benim meşhur iSkimmer’larımın ve donanımsal modifiye (Hardware Modding) yaptığım tüm o Ingenico POS cihazlarının ruhunu oluşturan o küçük ama devasa kodu sizinle paylaşıyorum. Tarih 2008. Bankalar, ATM’lerine “Jitter-Proof” gibi cafcaflı isimler taktıkları güvenlik önlemleriyle övünüyor, kartı içeri alırken titreterek, durdurarak bizim gibi “meraklıları” alt edeceklerini sanıyorlardı. Onlar sorunu kaba kuvvetle çözmeye çalışırken, ben zarafeti seçtim. Çünkü racon bunu gerektirirdi.
Mesele, kartı tek seferde ve kusursuzca okumak değildi. Mesele, sistemin kendisiyle çelişmesini sağlamaktı. Aşağıda temelini anlatacağım rutin, tam da bunu yapar.
Sistemin Damarlarına Sızmak: Düşük Seviye ADC Okuması
Bu kod, standart okuma protokollerini bir kenara atar. Doğrudan mikro denetleyicinin ADC (Analog-Dijital Dönüştürücü) kanalını kullanarak, manyetik şeritteki o en zayıf, en ham manyetik fısıltıları bile duyar. ATM, kartı içeri alırken istediği kadar titretsin, istediği kadar dursun. Benim rutinim, o kaos anında “okuyabildiği kadarını” okur. Yarım bir bit mi, çeyrek bir veri parçası mı? Hiç fark etmez. Hepsini bir kenara not eder.
Asıl sihir, kart dışarı çıkarken başlar. Kullanıcı kartını çekerken veya ATM kartı geri verirken yaşanan o ikinci titreşimli dansta, rutin tekrar devreye girer. Kart girerken alınan o bölük pörçük verilerle, çıkarken yakalananları birleştirir. Bir yapbozun parçaları gibi, eksik kısımları tamamlar ve sonunda size %99.99 doğrulukla, tam ve hatasız manyetik şerit bilgisini sunar.
Ve tüm bunları ne kadar bir hızda yaptığını biliyor musunuz? Sadece 500kHz. Evet, yanlış okumadınız, KiloHertz! Bu, neredeyse hiç enerji harcamadan, aylarca, hatta yıllarca tek bir pille çalışabilecek bir verimlilik demekti.
Contr-AntiSkimmer: Kalkanı Dinlemek, Gürültüyü Anlamak
Ama asıl gurur duyduğum, imzamı attığım sanat eseri bu değildi. O, benim “Contr-AntiSkimmer” adını verdiğim felsefeydi. Bankalar, skimmer’ları engellemek için ATM’lerin kart yuvası etrafına sürekli olarak manyetik sinyal bozan, parazit yayan bir elektromanyetik kalkan oluşturduklarını sanıyorlardı. Zekice bir savunmaydı. Peki ya o savunma silahını, bir kalkan olarak değil de, bir fener olarak kullansaydık?
Plan, şeytani bir basitliğe dayanıyordu. Kalkanla savaşmak yerine, onu dinlemeliydim. Modifiye ettiğim özel bir okuyucuyla, günlerce anti-skimmer korumalı bir ATM’nin yaydığı o manyetik gürültüyü ve seviyesini kaydettim. Zihnimin laboratuvarında o kayıtlara gömüldüm ve aradığım o an geldi. Zihnimde bir ampul değil, bir nükleer santral aydınlanmıştı.
Keşfim şuydu: ATM'nin savunma için sürekli yaydığı elektronik kalkanın yarattığı sabit manyetik gürültünün voltajı ile, o gürültünün içinden geçen gerçek bir kart okumasının yarattığı anlık voltaj arasında milimetrik, ama tespit edilebilir bir fark vardı.
Mevcut kart okuma rutinimde kullandığım Analog Dijital Çevirici (ADC), zaten bu tür bir hassas dinlemeye hazırdı. Tek yapmam gereken, yazılımı bu iki voltaj seviyesini ayırt edecek şekilde yeniden düzenlemekti. Artık skimmer, kalkanın yarattığı sürekli gürültüyü "arka plan sesi" olarak görmezden geliyor ve sadece gerçek kartın yarattığı o değerli sinyale odaklanıyordu. Kalkan, artık bir engel değil, avın ne zaman yaklaştığını haber veren bir işaretçiydi.
Ve sonra o çılgınca fikir geldi: ATM’nin yaydığı o elektromanyetik kalkan… O bir enerji kaynağıydı. O kalkanın yarattığı zayıf voltaj, hasat edilebilirdi. Bu, devre üzerindeki küçücük bir kapasitörün sürekli şarj edilebileceği ve pilsiz, sonsuza kadar çalışabilecek bir skimmer demekti. Düşmanınızın sizi durdurmak için harcadığı enerjiyi, kendi varlığınızı sürdürmek için bir yakıta dönüştürdüğünüzü hayal edin. Felsefem buydu. Onların silahıyla, onların kalkanıyla onları vuracaktım.
Yakalanan o değerli bilgiler, Bluetooth veya IR (Kızılötesi) ile yakındaki bir iHost cihazına aktarılır, oradan da GSM hattı üzerinden benim sunucularıma doğru o meçhul yolculuğuna çıkardı.
Neden Şimdi?
Bu kod, artık bir müzelik eser. Teknoloji ilerledi, yöntemler değişti. Ama mantık, felsefe asla eskimez. Bu satırlar, bir zamanlar “imkânsız” denilen bir şeyin nasıl yapılabildiğinin kanıtıdır. Bir sistemin en güçlü savunmasının, onu yeterince derinden anlarsanız, aslında nasıl en büyük zafiyeti olabileceğinin dersidir.
Buyurun. Sanatımı inceleyin.
- Manyetik Şerit Okuma Rutini iSkimmer_readCard.c
- Okunan Manyetik Şerit Bilgisini Bluetooth ile iHost Cihazına Gönderme Rutini iSkimmer_btUpdate
- iSkimmer'i Hazır Hale Getirme Rutini iSkimmer_init.c
/*****************************************************************************************************************************//**
* @file iSkimmer_readCard.c
*
* @brief Kart Okuma İşlemi
*
* @details
* ADC'nin çektiği akım MCU'nun aynı hızda çektiği akımdan daha FAZLA olduğu için sleep fonksiyonlarına gerek yok!
* Sistem 590uA kadar çekiyor. Bu aslında ADC'nin o hızda çektiği akım, bu yüzden MCU'un çektiği akım'ın önemi kalmıyor
* ADC'yi bu hızda çalıştırmazsam kart okumadaki kalite düşüyor. Bu yüzden daha irq/dma/sleep kullanmak gereksiz
*
* Eski kullanılan OPAMP'un durağan çıkışı logic 1 idi. Okuma flux'ları 0 volt'a doğru çekilerek çalışıyordu
* Bu sebeple kare dalga hesaplanırken, çıkış için 2000 (2volt civarı), iniş için 200 (0.2v gibi) çalıştırılıyordu
* Aslında eski rutinde bir hata yapmışım, iniş ve çıkış değerlerini öyle bir oturtmuşum ki hata gizlenmiş
* Durağan Çıkışı 1 olan opamp kullanılmak istenirse, +3v'tan iniş ve +3v'a çıkışların kontrolü ile rutin
* çok daha kararlı çalışacaktır. İniş ve çıkışlar birbirlerini çok daha yakın değerler verecektir
* Yeni kullanılan OPAP durağan çıkışı logic 0 olarak çalışıyor ve okuma flux'ları 3 volt'a doğru çekilerek çalışıyor
* Osilaskoptan izlediğim kadarı ile sadece 0'dan ayrılışların ve 0'a dönüşlerin tespiti ile rutin daha
* kararlı çalışabilir ki yukarıda bunun notunu düştüm. Denemeler sonunda başarı çok yüksek oldu
* Artık kare dalga hesabı için sadece 400'den ayrılma ve 400'ün altına inme değeri kullanılıyor
* Rakamın 400 olması; osilaskop'ta durağan çıkışın 0.280v. civarı seyretmesi, 400'ün garanti bir
* değer olmasından ötürüdür. 300 değeri belki rakamları daha da kararlı hale getirecek olsa da, parazit
* vs. nedenlerden ötürü rakamın biraz daha yukarda seçilmesi daha mantıklı geldi
*
*
* Coded by: ChaO
*
********************************************************************************************************************************/
#include "iSkimmer_defs.h"
#include "iSkimmer_init.h"
#include "iSkimmer_btUpdate.h"
#include "em_adc.h"
#include "em_timer.h"
#include "em_emu.h"
/*
* testNewMagneticHead() Yeni bir manyetik kafa takıldığında voltaj ayarlarını gösterir
*
* Yeni bir manyetik kafa kullanılacağı zaman bu rutin çalıştırılır
* 100,000 defa boş ADC okuması yapılarak ARTI ve EKSİ parazit seviyesi ölçülür
* Parazit seviyesi, ARTI ve EKSİ flux'ların Threshold değerini belirlerken kullanılır
*
* Kart geçirilerek manyetik kafanın hangi seviyelerde voltaj ürettiği gözlemlenir
*
* Örneğin developing'de kullanılan kart okuyucu:
* ADC Oversampling = 4'de ve Referans 1v25 iken:
* NegativeDistorition = -4 -6 civarı (5 kabul ediyorum)
* PositiveDistorition = 12 14 civarı (13 kabul ediyorum)
*
* Şöyle bir threshold hesaplıyorum:
NegativeThreshold = NegativeDistorition 2 = -5 * 2 = -10
PositiveThreshold = PositiveDistorition 2 = 13 * 2 = 26
*
*/
void testNewMagneticHead(void)
{
while (1) {
int maxDeger = 0, minDeger = 0;
int deger = 0;
for (int i = 0; i < 20000; i++)
{
while ((ADC0->IF & ADC_IF_SINGLE) == 0) ; /* Veri hazır olana kadar bekle */
deger = ADC_DataSingleGet(ADC0); /* Veriyi oku */
if (deger > maxDeger)
{ maxDeger = deger; }
if (deger < minDeger)
{ minDeger = deger; }
}
xprint("Min: %d - Max: %d", minDeger, maxDeger);
}
}
/*
* Buffer'ı Ters Yüz edilir
* Tek bir pass ile tüm buffer'ı ters yüz yapar
* Toplam uzunluğun yarısı kadar döngü yapılır
* Toplam uzunluk tek olsa da, bu en ortadaki değere denk gelir
* Rutin ona dokunmaz çünkü baştan da sondan da ortadadır
*
*/
static __INLINE void tersYuz(readBufferTypeDef *birSifir)
{
uint8_t tmpI; /* Kova olarak kullanılan byte */
for (uint32_t i = 0; i < (birSifir->counter / 2); i++) {
tmpI = birSifir->data[i]; /* Baştaki değeri kovaya koy */
birSifir->data[i] = birSifir->data[birSifir->counter -1 - i]; /* Sondaki değeri başa koy */
birSifir->data[birSifir->counter -1 - i] = tmpI; /* Baştaki değeri sona koy */
}
}
/*
cevir10() io içersindeki verileri *birSifir 'a dizerek 1 ve 0 haline getirir
* sondanBasa = true
* Dizme işlemini sondan başa doğru yapar
* Insert'in başındaki sıfırların okunamaması halinde dahi eğer 11010 okunabilmişse, kart başının tespitini mümkün kılar
*
* sondanBasa = false
* Dizme işlemini baştan sona doğru yapar
* Eğer kart takılırken durdurulup devam edilirse, baştan dizme yapıldığından kart başının tespiti mümkün olur
*
* İlk işlem olarak SondanBasa yapılması daha doğrudur!
*
*/
void cevir10(readBuffer_TypeDef io, readBuffer_TypeDef birSifir, bool ilkHatadaDur, bool sondanBasa)
{
uint8_t runningZero, /* Sıfırların sürelerinin hesaplandığı değişebilen ortalama */
tmpI,tmpI2; /* Her bir okuma verisinin depolandığı ve üzerinde işlem yapılan değişken */
uint16_t hesaplanmaYeri;
birSifir->counter = 0; /* birSifir'ı reset et */
if (sondanBasa) /* İlk veya Son 3 biti kullanma, vParity hatası çıkabiliyor, 1 ve 0'lar doğru yerleşmeyebiliyor */
{ hesaplanmaYeri = io->counter - 3; } else
{ hesaplanmaYeri = 3; }
runningZero = io->data[hesaplanmaYeri];
while (true) {
tmpI = io->data[hesaplanmaYeri]; /* Okuma verisi tmpI'de depolanır */
if (tmpI == MAX_FLUX_VALUE) { /* Eğer MAX_FLUX_VALUE bulunduysa */
if (ilkHatadaDur) { break; } else /* ilkHataDur ile Hatada durulması isteniyorsa DUR */
{ goto atla; } /* Max Flux değeri kartın durdurulduğunu söyler. Bunu artı yada eksi olarak değerlendirme, atla */
}
if (tmpI > (runningZero 75 / 100)) { / 0 bulundu, validData'yı güncelle */
runningZero = (runningZero + tmpI) / 2; /* Bulunan sıfırın süresine göre ortalamayı tekrar hesapla */
birSifir->data[birSifir->counter++] = 0;
} else {
if (sondanBasa)
{ tmpI2 = io->data[hesaplanmaYeri - 1]; } else /* İkinci verinin de 1 olma zorunluluğu var */
{ tmpI2 = io->data[hesaplanmaYeri + 1]; }
if (tmpI2 <= (runningZero * 75 / 100)) {
birSifir->data[birSifir->counter++] = 1;
runningZero = (runningZero + (tmpI + tmpI2) ) / 2; /* Bulunan 2 adet birin toplam süresine göre ortalamayı tekrar hesapla */
if (sondanBasa)
{ hesaplanmaYeri--; } else
{ hesaplanmaYeri++; }
} else { } /* Bir hatalı da olsa devam etsin. Daha önce break ile durduruyordum */
}
atla:
if (sondanBasa)
{ if ( (hesaplanmaYeri--) <= 3 ) { break; } } else /* Baştaki 3 byte'ı alma */
{ if ( (hesaplanmaYeri++) >= io->counter-1-3 ) { break; } } /* Sondaki 3 byte'ı alma */
}
}
/*
* isPartity1() Horizantal parity hesaplamasının en kısa yolu
* Okunan her bir verinin, olması gereken parity bitleri önceden kayıtlıdır
* İlgili verinin parity bitinin 1 olup olmadığı basitçe sorgulanır
*
*/
static __INLINE bool isParity1(uint8_t deger)
{
if ( (deger == 0) || (deger == 3) || (deger == 5) || (deger == 6) ||
(deger == 9) || (deger == 10) || (deger == 12) || (deger == 15) )
{ return true; } else
{ return false; }
}
/*
* _decode() birSifir buffer'daki veriyi decode etmeye çalışır
* birSifir buffer'da hem baştan sona, hemde tersYuz ederek sondan başa doğru kart yönünü bulmaya çalışır
*
* Kesin okuma yönü tespiti yapar
* readBuffer.dataTipi verilerini düzenler
*
* Decode edilen veriler readBuffer'a sıkıştırılarak yazılır
*
* Decode edilemediğinde, buffer 2 defa tersYuz() yapıldığından, başlangıç pozisyonuna gelir
*
*/
static __INLINE void decode(readBufferTypeDef *birSifir)
{
uint8_t decoded[MAX_SKIMMER_RECBUF_SIZE / 5]; /* 5 bit olduğu için 5'e bölerek olabilecek en uzun buffer tanımlanır */
uint16_t cntDecoded, cntYer;
uint8_t tmpI, dongu;
uint8_t vParity;
bool hParityOK, vParityOK;
for (dongu = 0; dongu < 2; dongu++) {
cntDecoded = 0; cntYer = 0;
while (cntYer < birSifir->counter) { /* İlk 1'i bul */
if (birSifir->data[cntYer] == 1) { break; } /* Bir bulunduysa dur */
cntYer++; /* Kontrol edildikten sonra arttır ki, tam 1'in yerini göstersin */
}
/* Start sentinel ve Valid Card Data (3,4,5,6) arka arkaya olma durumunu sına */
uint8_t ss = (birSifir->data[cntYer]) | (birSifir->data[cntYer+1] << 1) | (birSifir->data[cntYer+2] << 2) | (birSifir->data[cntYer+3] << 3) | (birSifir->data[cntYer+4] << 4);
uint8_t vc = (birSifir->data[cntYer+5]) | (birSifir->data[cntYer+6] << 1) | (birSifir->data[cntYer+7] << 2) | (birSifir->data[cntYer+8] << 3) | (birSifir->data[cntYer+9] << 4);
if ( (ss == 0x0B) && ( (vc == 0x13) || (vc == 0x04) || (vc == 0x15) || (vc == 0x16) ) ) {
if (dongu == 0) {
xprint("Kesin okuma yonu bulundu -> Insert");
ioBuffer->dataTipi = dtKesinOkumaYonu | dtKesinInsert;
break;
} else {
xprint("Kesin okuma yonu bulundu -> Reject");
ioBuffer->dataTipi = dtKesinOkumaYonu;
break;
}
}
_tersYuz(birSifir);
}
if (dongu == 2) {
xprint("Kesin okuma yonu BULUNAMADI");
goto cikis;
}
vParity = 0; hParityOK = true; vParityOK = false;
while (true) {
tmpI = (birSifir->data[cntYer]) | (birSifir->data[cntYer+1] << 1) | (birSifir->data[cntYer+2] << 2) | (birSifir->data[cntYer+3] << 3);
if ( (birSifir->data[cntYer+4] ^ isParity1(tmpI) ) ) /* BITWISE XOR olsa da, sadece son bit kullanıldığı için logical XOR gibi çalışıyor */
{ hParityOK = false; break; } /* Eğer bulunan parity, olması gerekenden farklı ise dur */
{ vParity = vParity ^ tmpI; } /* XOR ile vParity hesapla */
if (tmpI == 0x0F) { /* En son end sentinel ise dur (Ondan sonra gelen vParity değerini hesaplama) */
tmpI = (birSifir->data[cntYer+5]) | (birSifir->data[cntYer+6] << 1) | (birSifir->data[cntYer+7] << 2) | (birSifir->data[cntYer+8] << 3);
if (tmpI == vParity) /* Hesaplanan vParity, Karttan okunan ile aynı */
{ vParityOK = true; } /* Default olarak False olan vParityOK true yap */
break;
}
decoded[cntDecoded++] = tmpI; /* Decode Buffer'a ekle */
cntYer += 5; /* Sonraki muhtemel karakter başına gel */
if ( (cntYer+4) > birSifir->counter ) { break; } /* Sonraki 5 toplam uzunluğu geçecekse dur */
}
if ( !hParityOK ) { xprint("hParity Hatasi"); } else
if ( !vParityOK ) { xprint("vParity Hatasi"); } else {
ioBuffer->dataTipi |= dtDecoded; /* Decode işlemi 100% başarılı */
ioBuffer->counter = 0; /* ioBuffer'ı decode edilmiş ve sıkıştırılmış veri ile doldur */
for (int i = 1; i <= (cntDecoded - 1); i = i +2) { /* Decoded Buffer'da ilk byte'ı atla bu yüzden 1'den başla ve -1 */
tmpI = decoded[i] << 4; /* İlk byte'ı al ve sola kaydırarak sonraki 4 bit için yer aç */
/*
* Eğer ikinci 4 bitlik veri data limitini geçiyorsa, 1111 olarak kodla
* Yani buffer'ın son byte'ının son 4 biti 1111 olsun
*
* Toplam veri miktarı TEK sayıda ise, mecburen son veri tek başına kalır
* Bu durum bu şekilde çözüme kavuşturulur.
* Sıfır bırakırsam diğer sıfırlarla karışıyor. Oysaki 1111=15=ENDSENTINEL verinin içinde olmadığından karışmaz
*
*/
if (i + 1 <= (cntDecoded - 1) )
{ tmpI += decoded[i + 1]; } else
{ tmpI += 0x0F; }
ioBuffer->data[ioBuffer->counter++] = tmpI;
}
BAS_SKIMMER_BUFFER;
}
cikis: { }
}
/*
* decodeTrack()
*
* Bu rutinin asıl amacı, kartın çıktığının tespitidir
* Çünkü iHost çıkış yapan kart için camRecord() yapmaması enerji tasarrufu için önemlidir
*
* Bu amacı başarmak için ilk 10 bit'in Start Sentinel ve Valid Card Data Number olması sorgulanır
* Tüm kartlar 3, 4, 5 ve 6 ile başlar ve herbirinin kendine göre parity'si vardır
*
* Neticeten 5bit Start Sentinel ve ayrıca 5bit Valid Card Data Number kontrolü
* Kartın başının nerede olduğunu kesin olarak tespiti için son derece yeterlidir
*
* End Sentinel'in tespitinde birçok sorun olduğundan dolayı kullanılmasından vazgeçilmiştir
* Güvenilir bir End Sentinel ancak kart başından sonuna kadar HATASIZ okuma yapıldığında tespit edilebilmektedir
* Tersten okuma, son 5 bit'in kartın chksum bilgisi olduğundan dolayı
* ve sonunda bulunan sıfırların, kart sonu sıfırlar ile karışmış olmasından dolayı
* tersten okunarak End Sentinel tespit edilemez
*
* Tüm 4+1 bitlik verilerin tersten encode edildiği hatırlanmalı
* Start Sentinel = 0x0D (Parity 0) 0x0B
* 3 Amex = 0x03 (Parity 1) 0x13
* 4 Visa = 0x04 (Parity 0) 0x04
* 5 Master = 0x05 (Parity 1) 0x15
* 6 Local = 0x06 (Parity 1) 0x16
*
* POSSIBLEINSERT ve POSSIBLEREJECT
* Bu kısım kart başı veya sonundaki 0'ların sayımını yapar
* Kartın başı veya sonunu MUHTEMELEN tespit eder
* Possible Insert veya Possible Reject'e karı verir
*
* Possible Insert ve Possible Reject durumları sadece en son çare olarak kullanılacaktır
*
* Okuma testlerine göre minimum ve maksimum veriler şu şekildedir:
* Kart başında 19 - 23 adet sıfır bulunmaktadır
* Kart sonunda 40 - 52 adet sıfır bulunmaktadır
*
* Dolayısı ile en garanti yol;
* - Kartın başı veya sonunda EN AZ 30 adet sıfır varsa bu kartın sonudur
* - Sonu kullanılır çünkü kart takılsa da çıkartılsa da;
* Kartın sonu, okuyucu motoru tarafında çekilerek/itilerek okunur
* Bu okuma daha güvenilirdir
* Tersi olarak;
* Kartın başı, kurban tarafından çekilerek/itilerek okutulur
* Bu okuma da sorun çıkma ihtimali daha yüksektir
*
* Bulunan 30 adet sıfır:
* Okuma işleminin başındaysa okuma yönü REJECT'tir
* Okuma işleminin sonundaysa okuma yönü INSERT'tir
*
*/
static __INLINE void possibleDirection(readBufferTypeDef *birSifir)
{
uint16_t yer;
yer = 0;
for (uint8_t i = 0; i < 2; i++) {
while (yer < birSifir->counter)
{ if (birSifir->data[yer++] == 1) { break; } }
if (yer > 30) {
/*
* UNUTULMAMALIDIR Kİ:
*
* Decode edilemediğinde, buffer 2 defa tersYuz() yapıldığından, başlangıç pozisyonuna gelir
*
*/
if (i == 0)
{ ioBuffer->dataTipi = dtPossibleDirection; } else
{ ioBuffer->dataTipi = dtPossibleDirection | dtPossibleInsert; }
break;
}
_tersYuz(birSifir);
}
if (ioBuffer->dataTipi & dtPossibleDirection) {
if (ioBuffer->dataTipi & dtPossibleInsert)
{ xprint("Possible okuma yonu bulundu -> Insert"); } else
{ xprint("Possible okuma yonu bulundu -> Reject"); }
} else
{ xprint("Possible okuma yonu BULUNAMADI"); }
}
void decodeTrack(void)
{
cevir10(ioBuffer, birSifir1, false, false); /* Baştan Sona olarak 1 ve 0 yap, insert'i direkt okuma ihtimali daha yüksek */
_decode(birSifir1); /* Decode yapmayı dene */
ioBuffer->dateTime = CRYOTIMER_CounterGet(); /* dateTime'ı set et */
if ( !(ioBuffer->dataTipi & dtDecoded) ) { /* Decode yapılamadıysa */
if ( !(ioBuffer->dataTipi & dtKesinOkumaYonu)) { /* Kesin Okuma Yönü tespit edilemediyse */
{ _possibleDirection(birSifir1); } /* Possible Direction tespiti yapmaya çalış */
}
}
}
/*
kopyala2bt() buffer'ı btUpdateBuffer'a kopyalar
* Her kayıt başına bir Record Header koyar
* (6 byte)
* Sonuna kayıt verilerini koyar (decode veya değil)
*
*/
static __INLINE void kopyala2bt(readBuffer_TypeDef *buffer)
{
btUpdateBuffer->data[btUpdateBuffer->counter++] = (uint8_t) (buffer->counter >> 8); /* Counter */
btUpdateBuffer->data[btUpdateBuffer->counter++] = (uint8_t) buffer->counter; /* Counter */
btUpdateBuffer->data[btUpdateBuffer->counter++] = (uint8_t) buffer->dataTipi; /* Data Tipi */
btUpdateBuffer->data[btUpdateBuffer->counter++] = (uint8_t) (buffer->dateTime >> 24); /* DateTime 3 byte */
btUpdateBuffer->data[btUpdateBuffer->counter++] = (uint8_t) (buffer->dateTime >> 16); /* DateTime 3 byte */
btUpdateBuffer->data[btUpdateBuffer->counter++] = (uint8_t) (buffer->dateTime >> 8); /* DateTime 3 byte */
for (uint16_t i = 0; i < buffer->counter; i++) /* buffer.data'yı btUpdateBuffer'a ekle */
{ btUpdateBuffer->data[btUpdateBuffer->counter++] = buffer->data[i]; }
}
/*
* Insert ise:
* Flash'ta bulunan veri btUpdateBuffer'a kopyalanır
* Flash silinir
* "Decode Yapıldıysa" yeni veri de btUpdateBuffer'a eklenir
* "Decode Yapılamadıysa" Flash kaydı yapılır
*
* Buraya gelmesi için Kesin Reject veya Possible Reject tespit EDİLEMEMİŞ OLMASI YETERLİDİR
* Okunamayan INSERT için camRecord() yapılabilmesi için bu şekilde düşünülmüştür
* Okunamayan REJECT'in de buraya gelme ihtimali vardır (Unknown durumu)
* Flash'taki mevcut INSERT'i silmemesi için önce bu durum kontrolü yapılmaktadır
*
*/
void doInsertProcess(void)
{
flashOku(birSifir1, true); /* Flash'taki Insert verisini oku */
flashOku(birSifir2, false); /* Flash'taki Reject verisini oku */
if ( (birSifir1->counter != 0xFFFF) && /* Eğer Flash'ta Insert verisi mevcutsa VE */
(birSifir2->counter == 0xFFFF) ) { /* Eğer Flash'ta Reject verisi YOKSA */
xprint("Flash'ta Insert var ama Reject yok. (Tuhaf) Insert verisi btUpdateBuffer'a kopyalandi");
kopyala2bt(birSifir1); /* Flash'taki Insert verisini btUpdateBuffer'a kopyala */
}
if ( (birSifir1->counter != 0xFFFF) && /* Eğer Flash'ta Insert verisi mevcutsa VE */
(birSifir2->counter != 0xFFFF) && /* Eğer Flash'ta Reject verisi mevcutsa VE */
( !(birSifir2->dataTipi & dtDecoded)) ) { /* Eğer Flash'ta Reject verisi Decode YAPILAMADIYSA */
kopyala2bt(birSifir1); /* Insert verisini btUpdateBuffer'a ilk kayıt olarak kopyala */
xprint("Flash'taki Insert verisi btUpdateBuffer'a kopyalandi");
}
if (birSifir2->counter != 0xFFFF) { /* Eğer Reject verisi mevcutsa */
kopyala2bt(birSifir2); /* Her halukarda btUpdateBuffer'a kopyala */
xprint("Flash'taki Reject verisi btUpdateBuffer'a kopyalandi");
}
if (birSifir1->counter != 0xFFFF) /* Eğer Flash'ta Insert verisi varsa (Flash doluysa) */
{ flashSil(); } /* Flash'ı siler */
if (ioBuffer->dataTipi & dtDecoded) { /* Decode Yapıldıysa */
kopyala2bt(ioBuffer); /* ioBuffer'ı da btUpdateBuffer'a ekle */
xprint("Mevcut okuma Decode oldugu icin btUpdateBuffer'a kopyalandi");
} else { /* Decode Yapılamadıysa */
xprint("Mevcut okuma Decode yapilamadigi icin Flash'a yaziliyor");
flashYaz(ioBuffer, true); /* ioBuffer'ı Flash'a yaz, INSERT = TRUE */
}
btUpdate(); /* Insert'den sonra btUpdate() mutlaka yapılmalı ki camRecord() yapılabilsin */
}
/*
* Reject ise:
* "Decode Yapılamadıysa" Analiz ve Flash kaydı yapılır
* "Decode Yapıldıysa" Flash kaydı yapılır
*
* Buraya gelinmesi için Kesin Reject veya Possible Reject detect edilmesi gerekir
* Dolayısı ile eğer zaten Reject verisi varsa bu tuhaf bir durumdur
* Enerji tasarrufu açısından btUpdate yapılmaz
* Flash temizlenir
* Mevcut Reject okuması atlanır
*
* Decode Yapılamadıysa VE Kesin Okuma yönü tespit edildiyse okuma analiz edilir
* Eğer kesin okuma yönü tespit edilemediyse Analiz'in başarılma ihtimali yok
* Çünkü reject'te önemli olan;
* okumanın en sonunda yeralan Start Sentinel ve kalan verilerdir
*
*/
void doRejectProcess(void)
{
readBuffer_TypeDef _tmpIO; /* Geçici Insert verisinin depolanacağı buffer */
readBuffer_TypeDef tmpIO; / Buffer pointer */
tmpIO = &_tmpIO; /* Pointer init */
flashOku(birSifir2, false); /* Flash'tan Reject verisini oku */
if (birSifir2->counter != 0xFFFF) { /* Eğer flash'ta Reject verisi zaten varsa bu tuhaf bir durumdur */
xprint("Flash'ta zaten Reject verisi var. Bu tuhaf bir durumdur. Mevcut okuma atlaniyor");
flashSil(); /* Bu durumdan TEMİZ çıkmak için Flash silinir */
return; /* Mevcut okuma atlanır */
}
flashOku(tmpIO, true); /* Flash'tan insert verisini oku - tmpIO KULLANILIR YOKSA MEVCUT OKUMA SİLİNİR */
if (tmpIO->counter == 0xFFFF) /* Eğer Insert verisi bulunamadıysa */
{ xprint("Flash'ta Insert verisi bulunamadi. (Insert Decode yapilip gonderilmis) Mevcut okuma atlaniyor"); return; }
if ( !(ioBuffer->dataTipi & dtDecoded) ) { /* Mevcut okuma Decoded değilse */
if (ioBuffer->dataTipi & dtKesinOkumaYonu) { /* Kesin okuma yönü tespit edilemediyse */
bool bulundu = false; /* Insert ve Reject okumaların uçlarının eşleşme sağladığı bilgisi */
xprint("Mevcut Reject okuma Sondan Basa Decode ediliyor");
memcpy(birSifir2, ioBuffer, sizeof(_ioBuffer)); /* Mevcut Reject verisi birSifir2'ye kopyalanır */
cevir10(ioBuffer, birSifir2, true, true); /* Mevcut okumayı SONDAN BAŞA olarak DECODE ET, ilkHatadaDur = true */
/* Sondan başa dizildiği için tersYuz yapmaya gerek yok */
// readBuffer_TypeDef birSifir = birSifir2; BAS_BIRSIFIR; / Debug için birSıfır bas */
if (birSifir2->counter < 20) /* Sondan başa dizildiğinde analiz için yeterli veri yoksa çıkılır */
{ xprint("Mevcut Reject bilgisi analiz icin yeterli degil. Vazgeciliyor"); goto cikis; }
xprint("Eski Insert okuma Sondan Basa Decode ediliyor");
cevir10(tmpIO, birSifir1, true, true); /* tmpIO'daki Insert okuma bilgisini SONDAN BAŞA olarak DECODE ET, ilkHatadaDur = true */
_tersYuz(birSifir1); /* Sondan başa dizim yapıldığı için tersYuz() yap ki düzelsin */
// birSifir = birSifir1; BAS_BIRSIFIR; /* Debug için birSıfır bas */
if (birSifir1->counter < 20) /* Sondan başa dizildiğinde analiz için yeterli veri yoksa çıkılır */
{ xprint("Flashtaki Insert bilgisi analiz icin yeterli degil. Vazgeciliyor"); goto cikis; }
uint16_t yer = 2; /* İlk 2 bit hatalı olabilir, başlangıç olarak 3. biti seç */
while (yer < birSifir1->counter - 20) { /* 16bit zaten kontrol ediliyor. Kilitlenme olmasın diye, sona yaklaşıldıysa dursun */
if ( (birSifir2->data[birSifir2->counter-1-17]) == (birSifir1->data[yer]) &&
(birSifir2->data[birSifir2->counter-1-16]) == (birSifir1->data[yer+1]) &&
(birSifir2->data[birSifir2->counter-1-15]) == (birSifir1->data[yer+2]) &&
(birSifir2->data[birSifir2->counter-1-14]) == (birSifir1->data[yer+3]) &&
(birSifir2->data[birSifir2->counter-1-13]) == (birSifir1->data[yer+4]) &&
(birSifir2->data[birSifir2->counter-1-12]) == (birSifir1->data[yer+5]) &&
(birSifir2->data[birSifir2->counter-1-11]) == (birSifir1->data[yer+6]) &&
(birSifir2->data[birSifir2->counter-1-10]) == (birSifir1->data[yer+7]) &&
(birSifir2->data[birSifir2->counter-1-9]) == (birSifir1->data[yer+8]) &&
(birSifir2->data[birSifir2->counter-1-8]) == (birSifir1->data[yer+9]) &&
(birSifir2->data[birSifir2->counter-1-7]) == (birSifir1->data[yer+10]) &&
(birSifir2->data[birSifir2->counter-1-6]) == (birSifir1->data[yer+11]) &&
(birSifir2->data[birSifir2->counter-1-5]) == (birSifir1->data[yer+12]) &&
(birSifir2->data[birSifir2->counter-1-4]) == (birSifir1->data[yer+13]) &&
(birSifir2->data[birSifir2->counter-1-3]) == (birSifir1->data[yer+14]) &&
(birSifir2->data[birSifir2->counter-1-2]) == (birSifir1->data[yer+15])
) { bulundu = true; break; }
yer++;
}
if (!bulundu)
{ xprint("Analiz -> Hata. Veri uyusmasi tespit edilemedi"); goto cikis; } else
{ xprint("Analiz -> Basarili. Veri uyusmasi OK"); }
uint16_t lenCopy = birSifir1->counter - (yer+16);
memcpy(birSifir2->data + (birSifir2->counter-2), birSifir1->data + yer + 16, lenCopy);
birSifir2->counter = (birSifir2->counter - 2) + lenCopy;
// birSifir = birSifir2; BAS_BIRSIFIR; /* Debug için birSıfır bas */
_decode(birSifir2); /* Decode yapmayı dene */
if (ioBuffer->dataTipi & dtDecoded) { /* Decode yapıldıysa */
ioBuffer->dateTime = tmpIO->dateTime; /* ioBuffer'ın dateTime bilgisini FLASH'tan okunan Insert'e esitle */
BAS_SKIMMER_BUFFER; } else
{ xprint("Analiz -> Decode isleminde hata cikti") }
} else
{ xprint("Kesin okuma yonu tespit edilemedigi icin Analiz yapilmiyor") }
} else
{ xprint("Mevcut okuma zaten Decode yapildigi icin Analiz yapilmiyor"); }
/*
* Eğer Analiz başarılı olduysa;
* dateTime Insert'in dateTime'ı haline gelir
*
* Analiz başarılı olmadıysa;
* ioBuffer Reject dateTime halinde kalır
*
* ÖNEMLİ!!
* Eğer Reject direkt olarak okunursa, analiz yapılmayacağı için dateTime'ı da Reject olarak kalacaktır
* Bu durum veriler eve alındığında sanki camRecord() ile aralarında zaman uyumsuzluğu varmış gibi gözükse de
* çok sorun teşkil etmeyecektir. Çünkü Insert ile Reject arasında fazla zaman farkı yoktur
* Bu durumun artı avantajı ise, kaydın reject'in direkt okunarak alındığı bilgisini vermesidir
*
*
*/
cikis: /* Çıkışa gönderildiyse, her halukarda flashYaz yap */
flashYaz(ioBuffer, false); /* ioBuffer'ı Flash'a yaz, INSERT = FALSE = REJECT */
}
/*
* Switch ilk bırakıldığında LETIMER başlatılıp bir gecikme yaratılıyor
* Eğer bu gecikme sonunda eğer switch hala bırakılı durumdaysa
* "switchRelased" set ediliyor ve okuma sona eriyor
* Eğer gecikme sonunda switch basılı haldeyse
* okuma işlemi kesintiye uğramadan çalışmaya devam ediyor
*
* Kart içeri girerken switch'in bırakılması durumu hasıl olabilir
* Bu sebepten SwitchRelasedDelay X saniye gibi uzun bir süre belirlendi
* Bu süre içersinde Y adet 0 tespit edildiğinde
* okuma sonuçlandı bayrağı set edilecektir
* Bu sayede switch bırakılmasına rağmen boşuna bekleme yapılmayacaktır
*
* Kartın bir kısmının içeri takıldıktan sonra çıkartılması, sonra tekrar içeri itilmesi
* (fakat switch'in hiç bırakılmamış olması)
* aslında başlangıç kısmında bulunan 0'ların, bitişi gibi algılanmasına yol açacağı için
* Kart bitişi detect edilse dahi okuma işlemi, switch bırakılana kadar devam etmelidir
*
* Neticeten:
* Okuma sadece switch bırakılınca bitebilir
* Switch'in anlık bırakılması da okumanın bittiği anlamına gelmez ve bir gecikme süresi beklenir
* Bu gecikme süresi içersinde bitiş sıfırları tespit edildiyse veya
* Gecikme süresinin sonuna gelindiyse okuma sona erdirilir
* Eğer switch bırakıldıktan sonra tekrar basılırsa, timeout durdurulur
* Switch bırakılmamış gibi okuma işlemi durmaksızın devam eder
*
*
*/
volatile uint16_t RUNNINGZERO = 0;
volatile uint8_t BULUNANSONDAKISIFIR = 0;
volatile bool KARTSONUTESPITEDILDI = false;
static __INLINE void yazDeger(void)
{
uint16_t tmpI = 0; /* Timer verisinin alındığı ve kontrol ve işlemlerde kullanıldığı değişken */
tmpI = TIMER_CounterGet(TIMERSAYICI); /* Ne kadar geçtiği zaman bilgisini al */
TIMER_CounterSet(TIMERSAYICI, 0); /* TIMER'ın sayacını sıfırla */
if (tmpI > MAX_FLUX_VALUE) /* Geçen zaman max. flux değerinin üzerinde ise 1 timeout işaretle */
{ tmpI = MAX_FLUX_VALUE; }
ioBuffer->data[ioBuffer->counter++] = (uint8_t)(tmpI);
if (ioBuffer->counter > (20*5)) { /* Eğer en az X flux okuma yapıldıysa (Byte = 4+1 flux) */
if (tmpI > (RUNNINGZERO 75 / 100)) { / 0 Bulundu */
RUNNINGZERO = (RUNNINGZERO + tmpI) / 2; /* Bulunan sıfırın süresine göre ortalamayı tekrar hesapla */
if (++BULUNANSONDAKISIFIR > 10) /* En az 10 adet 0 tespiti yapıldıysa KARTSONUTESPITEDILDI! */
{ KARTSONUTESPITEDILDI = true; } /* Kart sonu sıfırları tespit edildi */
} else {
RUNNINGZERO = (RUNNINGZERO + (tmpI 2) ) / 2; / Bulunan "Birin 2 katına" göre ortalamayı tekrar hesapla */
BULUNANSONDAKISIFIR = 0; /* 1 Bulundu, bulunan sondaki sıfır sayısını reset et */
}
}
}
void readCard(void)
{
int32_t timeCounter = 0, /* Geçen zaman, oncekiZaman = Iki zaman arasındaki fark için tutucu */
deger = 0; /* ADC ölçümünden gelen veri */
bool ayrildi = 0; /* Kare dalga yapmada, min ve max limitlerin aşılma yönünü tayinde kullanılıyor */
ioBuffer = &_ioBuffer; /* ioBuffer'ı init */
birSifir1 = &_birSifir1; /* birSifir1'ı init */
birSifir2 = &_birSifir2; /* birSifir2'ı init */
btUpdateBuffer = &_btUpdateBuffer; /* btUpdateBuffer'ı init */
btUpdateHeader = _btUpdateHeader; /* btUpdateBufferHeader init */
ioBuffer->counter = 0; /* Read buffer'ı sıfırla */
ioBuffer->dataTipi = 0; /* Read buffer'ı sıfırla */
RUNNINGZERO = 0; /* Kart bitişi tespiti için flux zaman ortalaması */
BULUNANSONDAKISIFIR = 0; /* Kart bitişi tespiti için bulunan sıfır sayısı */
KARTSONUTESPITEDILDI = false; /* Kart bitişi tespit edildi bilgisi */
SWITCHRELASED = false; /* Başlangıçta Switch basılı */
SWITCHTIMEOUT = false; /* SwitchTimeout false */
xprint("Okuma basladi");
while (ioBuffer->counter < MAX_SKIMMER_RECBUF_SIZE) {
/*
* SwitchHandler switch bırakılınca SwitchTimer'ı SwitchTimeOut olacak şekilde kuruyor
* Burada sadece SWITCHTIMEOUT bilgisini kontrol yeterli
*
* SwitchHandler aynı zamanda, eğer switch'e tekrar basılırsa, SwitchTimer durduruluyor
*
*/
if (WATCHDOG_TIMEOUT) { xprint("Switch basili olsada, tum okuma timeout oldu"); break; }
if (SWITCHTIMEOUT) { xprint("Switch timeout oldu, kart sonu tespit edilemedi"); break; }
if ( (SWITCHRELASED) && (KARTSONUTESPITEDILDI) ) { xprint("Switch birakildi ve Kart sonu tespit edildi"); break; }
timeCounter++;
while ((ADC0->IF & ADC_IF_SINGLE) == 0) ; /* Veri hazır olana kadar bekle */
deger = ADC_DataSingleGet(ADC0); /* Veriyi oku */
if (!ayrildi) {
if (deger > POSITIVE_THRESHOLD) /* Positive flux tespit noktasında ise işleme al */
{ yazDeger(); ayrildi = !ayrildi; } /* Positive değeri yerrine yaz */
} else {
if (deger < NEGATIVE_THRESHOLD) /* Negative flux tespit noktasında ise işleme al */
{ yazDeger(); ayrildi = !ayrildi; } /* Negative değeri yerine yaz */
}
}
initReadCard(false); /* Kart okuma için kullanılan donanımları BURADA DA kapat */
if (ioBuffer->counter > 10) { /* Eğer yeterli sayıda okuma yapılabildiyse */
// BAS_SKIMMER_BUFFER; /* Ham buffer'ı bas */
decodeTrack(); /* Decode yapmayı dene */
// if (ioBuffer->dataTipi & dtDecoded) /* Decode başarılı ise decode'u bas */
// { BAS_SKIMMER_BUFFER; }
/*
* Okuma yönlerine bakılarak, flash'a kayıt, analiz ve btUpdate işlemlerine karar verilir
*
* Öncelikle "Kesin" okuma yönü test edilir
* Eğer "Kesin" okuma yönü tespit edilemediyse, "Possible" okuma yönü test edilir
* Eğer "Possible" okuma yönü de tespit edilemezse "Unknown" olarak işlem yapılır
*
*/
if (ioBuffer->dataTipi & dtKesinOkumaYonu) { /* Kesin okuma yönü tespiti yapıldı */
if (ioBuffer->dataTipi & dtKesinInsert) { /* Kesin okuma yönü Insert */
xprint("Kesin Insert");
doInsertProcess(); /* Insert İşlemlerini yap */
} else { /* Kesin okuma yönü Reject */
xprint("Kesin Reject");
doRejectProcess(); /* Reject İşlemlerini yap */
}
} else {
if (ioBuffer->dataTipi & dtPossibleDirection) { /* Possible okuma yönü tespiti yapıldı */
if (ioBuffer->dataTipi & dtPossibleInsert) { /* Possible okuma yönü Insert */
xprint("son tip: %d", ioBuffer->dataTipi);
xprint("Possible Insert");
doInsertProcess(); /* Insert İşlemlerini yap */
} else { /* Possible okuma yönü Reject */
xprint("son tip: %d", ioBuffer->dataTipi);
xprint("Possible Reject");
doRejectProcess(); /* Reject İşlemlerini yap */
}
} else { /* Kesin ve Possible okuma yönü tespiti yapılamadı */
xprint("Kesin ve Possible okuma yonleri tespit edilemedi, Possible Insert muammelesi yapilacak");
doInsertProcess(); /* Insert İşlemlerini yap */
}
}
}
}
/*****************************************************************************************************************************//**
* @file iSkimmer_btUpdate.c
*
* @brief Okunan kart bilgisi Bluetooth'tan iHost'a gönderilir
*
* @details
* Statik skimmer ve host adresleri kullanılarak hızlı bağlantı yapar
* Hard coded characteristic handle'ları kullanılır ve discover yapmadan direk ulaşım sağlar
* Genel bir timeout değişkenine bağlı çalışır, kilitlenme, açık kalma vs. sorunu yaşamaz
*
* LFXO Init'in bitmesi 400ms civarı sürüyor. Cihaz reset edilir edilmez açılıyor (.timeout = 0 ile)
* Kart okuması bitip buraya kadar geldiğinde 400ms zaman geçmiş olduğundan EM2 DeepSleep ile veri gönderimi yapılıyor
*
*
* Coded by: ChaO
*
********************************************************************************************************************************/
#include "AllDevicesOrtak.h"
#include "AllDevicesInit.h"
#include "iSkimmer_defs.h"
#include "em_system.h"
#include "native_gecko.h" /* Bluetooth stack headers */
/*
* Özellikle Global olarak tanımlandı. btUpdate içersinde mümkün olduğunca değişken kullanılmıyor
* iHost kısmında tanımlayamadığım bir problem çıkınca (detay orada var) Global Tanımlama yöntemine gidildi
*
*/
uint8_t connHandle; /* Bağlantı işlemlerinde kullanılır */
uint16_t runningCount = 0, /* Ne kadar gönderim yapıldıği */
byteToSend = 0, /* Gönderilecek ne kadar veri kaldığı */
maxMTU = 0; /* Tek seferde ne kadar veri gönderileceği */
struct gecko_cmd_packet* evt;
struct gecko_msg_system_get_bt_address_rsp_t* rspget;
/*********************************************************************************************************************************
* @brief Ana update rutini
*
* @details
* Bilinen host adresine bağlanır
* MTU'yu max. olarak ayarlar
* Hangi Trackların kullanıldığı ve uzunluklarını belirler ve CHR_00'a yazar/gönderir
* Her bir track için ilgili CHR'e yazar/gönderir
* Genel bir timeout'a bağlı çalıştığından kilitlenme sorunu yaşanmaz
* Zaten tüm cihazın Watch DOG koruması da vardır
* Watch DOG beslemesi özellikle yapılmaz ki rutin içersinde dead-loop olursa ve FEED mevcut ise cihaz reset edebilsin
*
********************************************************************************************************************************/
void btUpdate(void)
{
uint64_t sysID = SYSTEM_GetUnique(); /* System unique ID'sini al */
uint32_t dateTime = CRYOTIMER_CounterGet(); /* btUpdate anındaki DateTime */
btDurumlar_TypeDef btDurum = drDisconnected; /* Anlık ve sonuç btUpdate */
ZAMAN_SIFIRLA;
xprint("btUpdate() Basladi");
initBluetooth(); /* BT için gerekli Clock ve diğer init'leri başlat */
/* Eğer cihazın COMMON BT adresi henüz yazılmadıysa; yaz ve sistemi reset et (Yeni adres reset'ten sonra aktif oluyor) */
rspget = gecko_cmd_system_get_bt_address();
if ( (rspget->address.addr[0] != iSkimmerBTAddr.addr[0]) || (rspget->address.addr[5] != iSkimmerBTAddr.addr[5]) ) {
xprint("Cihazin BT Adresi set edildi. Reset ediliyor")
gecko_cmd_system_set_identity_address(iSkimmerBTAddr, 1); /* İkinci parametreye 0 yazarsan cihazın public adresini kullanmaya başlıyor */
gecko_cmd_system_reset(0);
}
/*
* 2 byte tüm btUpdateBuffer'ın toplam uzunluğu
* 3 byte btUpdate anındaki DateTime
* 3 byte System ID
* 2 byte btUpdate anındaki Battery Voltage (CİHAZ AÇILIŞINDA, DCDC BYPASS DURUMUNDAYKEN ÖLÇÜLÜR!)
*
* Toplam 10 byte
*
*/
btUpdateHeader[0] = (uint8_t) (btUpdateBuffer->counter >> 8);
btUpdateHeader[1] = (uint8_t) btUpdateBuffer->counter;
btUpdateHeader[2] = (uint8_t) (dateTime >> 24);
btUpdateHeader[3] = (uint8_t) (dateTime >> 16);
btUpdateHeader[4] = (uint8_t) (dateTime >> 8);
btUpdateHeader[5] = (uint8_t) (sysID >> 16);
btUpdateHeader[6] = (uint8_t) (sysID >> 8);
btUpdateHeader[7] = (uint8_t) sysID;
btUpdateHeader[8] = (uint8_t) (BATTERYVOLTAGE >> 8);
btUpdateHeader[9] = (uint8_t) BATTERYVOLTAGE;
//
// xprintC("\nbtUpdate:\n");
// for (uint16_t i = 0; i < btUpdateBuffer->counter; i++)
// { xprintC("%02X ", btUpdateBuffer->data[i]); }
// xprintC("\n");
runningCount = 0; maxMTU = 0; byteToSend = 0;
while ( (btDurum != drDone) && (btDurum != drAllDone) ) { /* drDone = hata halinde çıkış yapmak için, drAllDone = btUpdate'in başarılı olarak bittiği */
evt = gecko_peek_event();
switch (BGLIB_MSG_ID(evt->header)) {
/* Sistem ilk çalışmaya başladığında bir kez buraya gelir */
case gecko_evt_system_boot_id:
/* 8 Saniye'de TimeOut olunca rutinden çıksın */
gecko_cmd_hardware_set_soft_timer(32768 * 8, 0, 1);
/*
* MTU'yu max. olarak set edilir
*
* gecko_cmd_le_gap_set_set_conn_parameters:
min / max = 24 1.25 = 30ms gibi çok garanti bir süre seçtim.. 10'lu rakamlarda arada bazı paketler bozuluyor ve tekrar gönderiyordu
*
* (Bu değerler Default olarak 250ms) Süper Hızlandırdım
* Slave latency default olarak bırakıldı 0
Supervision timeout default olarak bırakıldı 100 10 = 1000
* Önerilen Max Değerler Şöyle:
* Connection interval: 7.5 ms - MTU size: 250 bytes
* Packet size: 170 bytes
* Ben 12.5ms'de 250bytes gönderiyorum, testlerde birsorun gözükmüyor
*
Değerler 12 1.25 = 15ms yapılsa da hız değişmiyor
* 24 değeri yeterince sınanmış ve gayet hızlı..
*
* DEĞİŞTİRMEYE GEREK YOK
*
*
* Bağlantı esnasında gecko_cmd_le_gap_set_discovery_timing parametreleri kullanılmakta
* Bağlantı kurulumu her 3 kanal'ı scan edilmesi ile yapılmakta
Herbir kanal scan süresini 1120 0,625 = 700ms olarak belirledim
* iHost 350ms civarında advertise paketi gönderiyor, her kanalda 2 advertise paketi yakalanabilsin
* Neticeten, default değerler ile 500ms "ortalama" ile seyreden bağlantı süresi 150-200ms "ortalama" civarına düştü
*
*/
gecko_cmd_gatt_set_max_mtu(247);
gecko_cmd_le_gap_set_conn_parameters(24, 24, 0, 100);
gecko_cmd_le_gap_set_discovery_timing(le_gap_phy_1m, 1120, 1120);
gecko_cmd_le_gap_connect(iHostBTAddr, le_gap_address_type_random, le_gap_phy_1m);
xprint("Host Scanning basladi");
break;
/* Timeout oldu */
case gecko_evt_hardware_soft_timer_id:
gecko_cmd_system_halt(1); /* Bağlantı yapmaya devam etmesin */
btDurum = drDone; /* Hata ile çıkış */
xprint("Timeout oldu, cikiyor!");
break;
/*
* Bağlantı kurulduğunda henüz MTU Anlaşması bitmemiş olsa dahi, btUpdateHeader'ı göndermede bir sorun yok
* Çünkü en düşük MTU büyüklüğü 23 bytes'dır ve btUpdateHeader için bu yeterlidir
*
*/
case gecko_evt_le_connection_opened_id:
xprint("Baglanti kuruldu");
connHandle = evt->data.evt_le_connection_opened.connection; /* Açılan Bağlantının Handler'ını al */
/*
* 8 Bit'lik direkt _btUpdateHeader'a pointer tanımlamazsam, gönderilen veriler hatalı oluyor
* iHost tarafından hatalı okunuyor
* Tek yolu bu
*
* Data bilgilerinin olduğu ilk paketi gönder - Standart BT_BUFFER_HEADER_SIZE boyutunda
*
*/
gecko_cmd_gatt_write_characteristic_value(connHandle, CHR_ID, sizeof(_btUpdateHeader), btUpdateHeader);
xprint("btUpdateBufferHeader gonderildi. Uzunlugu: %i", sizeof(_btUpdateHeader));
BAS_BTUPDATEBUFFER_HEADER;
break;
case gecko_evt_gatt_mtu_exchanged_id:
maxMTU = evt->data.evt_gatt_mtu_exchanged.mtu;
xprint("MTU degisimi bitti: %i", (int)evt->data.evt_gatt_mtu_exchanged.mtu);
break;
/*
* Önce btBufferHeader'ın gönderim bilgisi gelecektir.
*
* MTU anlaşması btBufferHeader'ın gelmesinden daha önce bitiyor (Analyzer sonuçları sürekli bu yönde).
* İlk gönderilen paket zaten MTU anlaşma paketi. Daha sonra btBufferHeader gönderiliyor ve önce MTU anlaşması bitiyor
*
* Daha sonra teker teker tüm track bilgileri gönderilir
* Her gelişinde en fazla 240 byte olarak gönderim yapılır
*
*/
case gecko_evt_gatt_procedure_completed_id:
/* Tüm verilerin gönderimi bitti, çıkış yapılır, Özellikle başa koyuyorum, rutinden sonra çalışmasın diye */
if (runningCount == btUpdateBuffer->counter) {
btDurum = drAllDone; /* Başarılı btUpdate */
gecko_cmd_le_connection_close(connHandle);
xprint("Veri gonderimi bitti, cikiliyor");
} else {
/*
* Bu kısım'a geldiğinde ilk btBufferHeader gönderilmiş olduğundan MTU'u da 247'de karşılıklı anlaşılma bitmiş oluyor
* Bu sebeple verilerde problem çıkması halinde burası kontrol edilmeli (Küçük bir ihtimal MTU 23 kalmış olabilir
* Gerçi API Manual'e göre MTU 247 DEFAULT halde, bu yüzden MTU'yu set etmeye bile gerek olmasa da, garantiye almak iyidir
*
* Eğer henüz MTU Anlaşması Sağlanmadı İse, default 23-3 olduğundan 20 kullanmak güvenli
* Max 247 olsa da, garanti olması için 240 byte gönderiliyor
*
*/
if (maxMTU < 30) { byteToSend = 20; } else
if (maxMTU > maxBLEPacketLength) { byteToSend = maxBLEPacketLength; }
/* Eğer Son Gönderme işlemi olacaksa */
if ( (runningCount + byteToSend) > btUpdateBuffer->counter )
{ byteToSend = btUpdateBuffer->counter - runningCount; }
gecko_cmd_gatt_write_characteristic_value(connHandle, CHR_ID, byteToSend, btUpdateBuffer->data + runningCount );
runningCount = runningCount + byteToSend;
xprint("Veri gonderildi. Uzunlugu: %i", byteToSend);
}
break;
default:
break;
}
gecko_sleep_for_ms(gecko_can_sleep_ms());
}
}
/*********************************************************************************************************************************
* @file iSkimmer_init.c
*
* @brief iSkimmer için başlangıç ayarları
*
*
* Coded by: ChaO
*
********************************************************************************************************************************/
#include "iSkimmer_defs.h"
#include "AllDevicesInit.h"
#include "AllDevicesOrtak.h"
#include "em_adc.h"
#include "em_timer.h"
#include "em_rmu.h"
#include "em_msc.h"
/*
* TIMERSWITCH ile switch'in bırakılmasından sonra geçebilecek maksimum süre tutulur
* Oneshot olarak çalışır
* Kendini init eder ve kapatır
*
*/
void TIMERSWITCH_INT_HANDLER(void)
{
TIMER_IntClear(TIMERSWITCH, TIMER_IF_OF); /* Interrupt bayrağını sil */
SWITCHTIMEOUT = true; /* TIMERSAYICI'nın timeout olduğu bilgisi */
TIMER_Reset(TIMERSWITCH); /* Timer'ı reset öncesine döndür */
CMU_ClockEnable(TIMERSWITCH_CLK, false); /* Timer'a clock'unu kapat */
}
void switchTimeout(uint32_t milisaniye)
{
if (milisaniye == 0) { /* Timer'ı durdurmak istenmiştir */
TIMER_Enable(TIMERSWITCH, false);
TIMER_Reset(TIMERSWITCH); /* Timer'ı reset öncesine döndür */
CMU_ClockEnable(TIMERSWITCH_CLK, false); /* Timer'a clock'unu kapat */
SWITCHTIMEOUT = false; /* Timerout bilgisini sıfırla */
goto cikis;
}
SWITCHTIMEOUT = false; /* Timerout bilgisini sıfırla */
TIMER_Init_TypeDef timerInit = TIMER_INIT_DEFAULT; /* Default değerleri yükle */
CMU_ClockEnable(TIMERSWITCH_CLK, true); /* Timer'a clock ver */
timerInit.oneShot = true; /* Timer tekseferlik çalışır */
timerInit.prescale = timerPrescale512; /* 512'ye bölerek yaklaşık milisayiyeye çevirmiş oluyorum HF/2 ÇALIŞIYOR */
TIMER_TopSet(TIMERSWITCH, milisaniye); /* Beklenecek toplam süreyi yaz */
TIMER_Init(TIMERSWITCH, &timerInit); /* Timer init ile otomatik başlasın */
NVIC_ClearPendingIRQ(TIMERSWITCH_IRQ); /* Bekleyen interrupt'ları kapat */
NVIC_DisableIRQ (TIMERSWITCH_IRQ); /* */
TIMER_IntEnable(TIMERSWITCH, TIMER_IF_OF); /* Timer Interrupt enable */
NVIC_EnableIRQ(TIMERSWITCH_IRQ); /* NVIC Timer Interrupt enable */
cikis: { }
}
void switchHandler(void)
{
GPIO_IntClear( GPIO_IntGet() ); /* Interrupt bayrağını sil */
if (!GPIO_PinInGet(SWITCH_PORT, SWITCH_PIN) ) { /* Switch'e tekrar basıldı */
SWITCHRELASED = false; /* Switch'e tekrar basıldı bilgisi */
switchTimeout(0); /* SwitchTimeout saymasını durdur */
} else {
SWITCHRELASED = true; /* Switch'in bırakıldığı bilgisi */
switchTimeout(SWITCH_RELASE_DELAY); /* Switch glitch veya yanlışlık veya ERKEN açılması durumuna karşı gecikme */
}
__DSB(); /* Peripheral MCU Clock'tan daha düşükse, FLUSH YAP */
}
void GPIO_EVEN_IRQHandler(void) { switchHandler(); }
void GPIO_ODD_IRQHandler (void) { switchHandler(); }
void initSwitchRelase(bool enable)
{
NVIC_ClearPendingIRQ(GPIO_EVEN_IRQn); /* Switch interrupt'ları kapat */
NVIC_ClearPendingIRQ(GPIO_ODD_IRQn ); /* */
NVIC_DisableIRQ (GPIO_EVEN_IRQn); /* */
NVIC_DisableIRQ (GPIO_ODD_IRQn ); /* Switch interrupt'ları kapat */
SWITCHRELASED = false; /* Başlangıçta switch bırakılmamış durumda */
if (enable) { /* Eğer enable yapılmak isteniyorsa */
GPIO_ExtIntConfig(SWITCH_PORT, SWITCH_PIN, SWITCH_PIN, true, true, true); /* Yükselen ve alçalan kenarda trigger yap (Active low) */
NVIC_EnableIRQ(GPIO_EVEN_IRQn);
NVIC_EnableIRQ(GPIO_ODD_IRQn );
}
}
void initTimerSayici(void)
{
CMU_ClockEnable(TIMERSAYICI_CLK, true); /* Timer'a clock ver */
TIMER_Init_TypeDef timerInit = TIMER_INIT_DEFAULT; /* Default değerleri yükle */
timerInit.prescale = TIMERSAYICI_PRESCALER; /* MCU hızına göre optimum prescale seç */
TIMER_Init(TIMERSAYICI, &timerInit); /* Timer başlasın */
}
/*
* ADC'nin Hazırlanması
*
* ADC Differential Çalışıyor (CH6, CH7 = PD6, PD7)
* Sürekli okur, ve güç tasarrufu yapmadan hiç warm-down yapmaz
*
* ADC Hızı sistem hızına eşit olduğu için (timebase0) 1 Cycle'da okuma yapar, bekleme ayarına gerek yok (acqtime)
*
* ADC Port/Pin GPIO'dan açılmaz! Disabled kalır, bu da ANALOG giriş demektir.
* GPIO'dan yapılan işlem Digital giriş/çıkış işlemidir. ADC analog bir peripheraldir
*
* ADC Oversampling değeri okuma başarısında çok önemli
* Yüksek oversampling değerleri, özellikle hızlı kart geçişlerinde flux'ları yakalayamıyor
* Daha önce bu durumu MCU hızına bağlı zannetmiştim ama oversampling düşünce ADC hızlı ölçüm yapıyor
* Bu yüzden flux'lara kolayca yetişebiliyor
* Ben MCU'yu hızlandırınca da flux'lara yetişebiliyor fakat bunun sebebi ADC Clock'unun da hızlanması
* Dolayısı ile asıl sorun ADC'nin hızlı ölçüm yapması, MCU'un hızlanması değiş
* (Ayrıca 1mhz'de çalışan MCU ile EFM32'de reader yapıldı ve çok iyi sonuç alındı
* Dolayısı ile EFR32 MCU hızı 2mhz gayet yeterli)
* Oversampling değerinin düşük tutulması ADC'yi hızlandırsa da çok olumsuz bir dezavantaj getiriyor:
* Düşük voltaj üretildiğinde (kartın yavaş çekilmesi, küçük manyetik kafa) flux'lar tespit edilemiyor
*
* Bu sebeple Oversampling'i olabilecek en max. tutmaya karar verildi
* 1mHz MCU için:
* 4x gayet güzel sonuç verdi
* 8x hızlu kart geçişinde flux'ları yakalayamıyor
*
* 2mhz MCU için:
* 8x gayet güzel sonuç verdi
* 16x hızlı kart geçişinde flux'ları yakalayamıyor
*
* MİNİ KAFA TAKILIP DENENDİĞİNDE, VOLTAJ SORUNU ÇIKARIYORSA, MCU HIZLANDIRILIP 8X KULLANILACAK
*
*
* adcRef1V25 kullanmam mecburi
* MCU 3v çalışırken adcRef2V5 veya adcRefVDD reference kullanmak risk arz ediyor
* Pil seviyesi düşünce tespit için kullanılan NegativeThreshold ve PositiveThreshold değerleri oynama yapabilir
* Çünkü ADC'nin ölçeceği voltaj seviyesi, referans olarak alınan voltaj seviyesine bağlıdır
* adcRef1V25 alınması, MCU'nun çalışabildiği her pil voltajında, ölçüm değerlerinin aynı olmasını sağlayacaktır
* adcRef1V25 kullanmamamın bir diğer sebebi de, tespit voltajı diğer referans voltajlara göre daha yukarda
* Örnegin Min. ve Max. ölçüler değerler adcRef1V25 ile 300'lü seviyelerdeyken, adcRefVDD ile 50'lere düşüyor
*
* Bu sebeple:
* adcRef1V25 Kullanımı ilerde kullanılabilecek çok küçük voltaj üreten manyetik kafaları kolayca destekleyecektir
*
*/
void initADC(void)
{
ADC_InitSingle_TypeDef singleInit = ADC_INITSINGLE_DEFAULT;
ADC_Init_TypeDef init = ADC_INIT_DEFAULT;
CMU_ClockEnable(cmuClock_ADC0, true); /* ADC'ye clock ver */
ADC0->CMPTHR = ADCCMPTHR_RESETVALUE; /* Compare Threshold'u default değerine al */
ADC0->SCANCTRL = ADC_SCANCTRL_REF_1V25; /* Errata, Single ile aynı voltage olmalı */
init.ovsRateSel = ADC_OVERSAMPLE; /* Her bir okumayı X defa yap, okuma kalitesini arttır */
init.timebase = ADC_TimebaseCalc(0); /* Timebase 0 seç */
init.warmUpMode = adcWarmupKeepADCWarm; /* ADC enerjisi sürekli açık kalsın */
ADC_Init(ADC0, &init); /* ADC Genel (single + scan) init et */
singleInit.resolution = adcResOVS; /* Birden fazla okumayı aktivite et */
singleInit.acqTime = adcAcqTime1; /* Okuma hızı, TimeBase(0)'da yavaşlatmaya gerek yok */
singleInit.diff = true; /* Differential çalıştır */
singleInit.rep = true; /* ADC sürekli okuma yapsın */
singleInit.reference = adcRef1V25; /* Reference olarak 1.5v kullan (Pil 3v, 2.5 riskli) */
singleInit.posSel = MAGHEAD_POS; /* ADC pozitif uç */
singleInit.negSel = MAGHEAD_NEG; /* ADC negatif uç */
singleInit.fifoOverwrite = true; /* ADC yeni veriyi fifo üzerine yazsın, yoksa reject yapıyor */
ADC_InitSingle(ADC0, &singleInit); /* ADC Single Init yap */
ADC_Start(ADC0, adcStartSingle); /* Başlat, Sürekli Oku */
}
void initReadCard(bool enable)
{
if (enable) {
xprint("initReadCard(Enable)");
GPIO_PinModeSet(MAGHEAD_PORT, MAGHEAD_POS_PIN, gpioModeDisabled, 0); /* Maghead pos'u disable yaparak analog seç */
GPIO_PinModeSet(MAGHEAD_PORT, MAGHEAD_NEG_PIN, gpioModeDisabled, 0); /* Maghead neg'i disable yaparak analog seç */
GPIO->P[MAGHEAD_PORT].OVTDIS |= (0x01 << MAGHEAD_POS_PIN); /* Maghead pos OVT'yi disable yap */
GPIO->P[MAGHEAD_PORT].OVTDIS |= (0x01 << MAGHEAD_NEG_PIN); /* Maghead neg OVT'yi disable yap */
GPIO_SlewrateSet (MAGHEAD_PORT, 7, 7); /* En güçlü, hızlı 7 */
GPIO_DriveStrengthSet(MAGHEAD_PORT, gpioDriveStrengthStrongAlternateStrong); /* En güçlü seçim, em_gpio'dan. GPIO strong 10mA and alternate function strong 10mA */
initADC(); /* ADC'yi başlat */
initTimerSayici(); /* Timer'ı başlat */
initSwitchRelase(true); /* Switch'in bırakılma interruptlarını kur */
} else {
xprint("initReadCard(Disable)");
ADC_Reset(ADC0); /* ADC'yi hardware reset sonraki duruma çevir */
CMU_ClockEnable(cmuClock_ADC0, false); /* ADC'nin clock'unu durdur */
TIMER_Reset(TIMERSAYICI); /* Timer'ı hardware reset sonraki durumuna çevir */
CMU_ClockEnable(TIMERSAYICI_CLK, false); /* Timer'ın clock'unu durdur */
initSwitchRelase(false); /* Switch Interrupt'larını kapat */
}
}
void initISkimmer(void)
{
uint32_t resetCause;
initDevice(); /* Tüm cihazlar için ortak init */
resetCause = RMU->RSTCAUSE; /* Olabildiğince hızlı şekilde reset sebebini öğren */
RMU_ResetCauseClear(); /* Reset sebebini sil */
(void) resetCause; /* İlerde belki kullanılır */
ZAMAN_SIFIRLA;
xprintC("\n\n\n****************** iSkimmer Basladi ******************\n");
BATTERYVOLTAGE = readBattery(); /* Henüz bypass durumundayken pil seviyesi ölçülür */
xprint("Bypass switch oncesi Pil voltaji: %d", BATTERYVOLTAGE);
//TODO:
// BYPASS SWITCH OFF!
// BYPASS SWITCH OFF!
// BYPASS SWITCH OFF!
// BYPASS SWITCH OFF!
GPIO_PinModeSet(SWITCH_PORT, SWITCH_PIN, gpioModeInputPullFilter, 1); /* Switch'i ayarla */
#ifdef DEBUG
xprint("Acilis yavaslamasi");
bekle(1000, true, 2); /* Debug Mod'a özel açılış yavaşlaması */
#endif
initReadCard(true); /* Kart okuma için gerekli init rutinleri, Enable = true */
}
/*********************************************************************************************************************************
* @brief EM4 Uyku Moduna Geçer
*
* @details
*
********************************************************************************************************************************/
void derinUyku(void)
{
initReadCard(false); /* Kart okuma için kullanılan donanımları kapat */
while( !GPIO_PinInGet(SWITCH_PORT, SWITCH_PIN) ) { /* Switch bırakılana kadar beklemeler yap */
xprint("Switch halen basili durumda, bekleniyor");
WDOG_Feed(); /* Köpeği besle, reset etmesin */
bekle(10000, true, 3); /* Belirlenen süre uyu */
}
xprint("EM4 uykusuna geciliyor")
xprint("Sleep... zzZZzz..ZZzz.zzzZZzz.zzzZZzz..");
EMU_EM4Init_TypeDef em4init;
em4init.em4State = emuEM4Shutoff; /* Shutoff kullanıyorum, emuEM4Hibernate de kullanılabilir */
em4init.retainLfxo = false; /* LFXO EM4'te çalışmasın */
em4init.retainLfrco = false; /* LFRCO EM4'te çalışmasın */
em4init.retainUlfrco = true; /* CRYO Timer EM4'te saymaya devam etmesi için ULFRCO çalışmalı */
em4init.pinRetentionMode = emuPinRetentionDisable; /* PinRetention'a ihtiyaç yok */
EMU_EM4Init(&em4init);
GPIO_EM4EnablePinWakeup(GPIO_EXTILEVEL_EM4WU1, GPIOEXTILEVEL_EM4WU1_DEFAULT); /* Switch ile uyanacağı bilgisi */
EMU_EnterEM4(); /* EM4 uykusuna geç */
}
void flashSil(void)
{
msc_Return_TypeDef ret;
MSC_Init(); /* MSC init */
ret = MSC_ErasePage((uint32_t *) USERDATA_BASE);
if (ret == mscReturnOk)
{ xprint("flashSil() basarili"); } else
{ xprint("flashSil() ERROR: %d", (int) ret); }
}
/*
Belirtilen uzunlukta veriyi Flash bölgeden buffer'a kopyalar
*
*/
void flashOku(readBuffer_TypeDef *buffer, bool insert)
{
uint32_t adresOfset = 0; /* Insert ve Reject'in okunacağı yer ofset değeri */
if (!insert) /* Eğer reject verisi ise 1024 byte ofset uygula */
{ adresOfset = 1024; }
memcpy(buffer, (uint32_t ) (USERDATA_BASE + adresOfset), sizeof(_ioBuffer) ); / sizeof(_ioBuffer) uzunluğu kadar. Diğeri pointer!! */
if (insert)
{ xprint("flashOku() -> Insert: %d (counter)", buffer->counter); } else
{ xprint("flashOku() -> Reject: %d (counter)", buffer->counter); }
}
/*
* Belirtilen uzunlukta veriyi Flash'a yazar
* Word yani "2 x 32bit = 8 byte" yazılma zorunluluğu var
* Hassas ayarlama yapılmak istendiğinde sonuna 0x00 yazarak 8'in katları döngüsü yapılabilir
* Uygulamada dummy kısım olduğu için son tarafların hiç önemi yok
*
* Her flashWrite() işleminden önce ERASE ALL MECBURİ
*
* Flash'ın:
* İlk 1024 byte'ı Insert için
* Son 1024 byte'ı Reject için ayrılmıştır
*
*/
void flashYaz(readBuffer_TypeDef *buffer, bool insert)
{
msc_Return_TypeDef ret;
uint32_t adresOfset = 0; /* Insert ve Reject'in yazılacağı yer ofset değeri */
MSC_Init(); /* MSC init */
if (!insert) /* Eğer reject verisi ise 1024 byte ofset uygula */
{ adresOfset = 1024; }
ret = MSC_WriteWord((uint32_t ) (USERDATA_BASE + adresOfset), buffer, sizeof(_ioBuffer)); / sizeof(_ioBuffer) uzunluğu kadar. Diğeri pointer!! */
if (ret == mscReturnOk) {
if (insert)
{ xprint("flashYaz() -> Insert: %d (counter)", buffer->counter); } else
{ xprint("flashYaz() -> Reject: %d (counter)", buffer->counter); };
} else {
if (insert)
{ xprint("flashYaz() -> Insert -- ERROR: %d", (int) ret); } else
{ xprint("flashYaz() -> Reject -- ERROR: %d", (int) ret); };
}
}