لقد قرأ الكثيرون رسالتي " RFID Emulator " ، حيث تحدثت بالتفصيل عن جهاز EM Marine ، وكيفية لف الهوائي ، وكيفية صنع محاكي RFID من ثلاثة أجزاء. لكن لنكن صادقين ، على الرغم من البساطة البارعة لهذا الجهاز ، من الصعب جدًا تكرار ذلك. ليس كل شخص لديه راسم الذبذبات في المنزل ، من أجل التقاط الرنين ، وهناك حاجة إلى مبرمج منفصل للبرامج الثابتة ATtiny85.
لذلك ، قررت أن أصنع مثل هذا المحاكي الذي يمكن حتى للطفل أن يكرره. تباع جميع المكونات في كل قرية تقريبًا. علاوة على ذلك ، يمكن توسيع وظائفها. على سبيل المثال ، يمكنك حفظ عدة بطاقات فيه ، أو يمكنك إضافة قارئ آخر وحفظ جميع البطاقات في جهاز واحد ، أو استخدامها من أجل ... فلنبدأ.
المعدات
كما قلت ، يجب إنشاء المحاكي باستخدام المكونات المتاحة التي يمكن الحصول عليها بسهولة. أولاً ، لنلقِ نظرة على دائرة المحاكي.
لدينا دائرة تذبذبية ، سنغلقها في وقت معين بواسطة ترانزستور ، وبالتالي سيتغير التيار في القارئ ، وسيستقبل البيانات المرسلة.
أصعب شيء بالنسبة لنا في هذا الصدد هو الدائرة التذبذبية المضبوطة على تردد 125 كيلو هرتز. وهناك حل بسيط للغاية حيث يمكنك الحصول عليه. يوجد قارئ علامات RFID لـ Arduino RDM6300 للبيع . لا يكلف القارئ سوى أجر ضئيل ، ويأتي بالفعل بهوائي ، ومكثف الرنين ملحوم بالفعل على السبورة. وبالتالي ، في الواقع ، نحتاج فقط إلى قارئ لجزئين: الملف ومكثف الرنين.
قارئ RDM6300 وموقع مكثف الرنين.
اشتريت هذا القارئ مقابل فلس واحد ، وهو ما لا يتناسب مع جهد لف وضبط الهوائي. أصعب عملية بالنسبة لنا هي فك هذا المكثف ولحامه بلوحة الدائرة. أعتقد أنه حتى طالب المدرسة الابتدائية يمكنه التعامل معها.
نتيجة لذلك ، نجمع كل شيء على لوح التجارب. لدي مقاومتان متوازيتان فقط لأنني لم يكن لدي مقاومات 10 كيلو أوم في متناول اليد ، ولكن فقط 20 كيلو أوم.
دارة مجمعة.
حسنًا ، دعنا نرى عن قرب كيف يبدو كل شيء. لقد خصصت بشكل خاص وشاحًا منفصلاً للمكثف ، حيث يتم لحامه مباشرة على إبر التثبيت التي يتم إدخالها في هذه المرتبة.
من أجل اختبار المحاكي ، فكرت في البداية في استخدام نفس RDM6300 (اشتريت اثنين منهم). وحتى في البداية فعل ذلك ، ولكن بعد ذلك قرر أنه بطريقة ما لم يكن جادًا ، أحدهم Arduina يقوم بتصحيح الآخر ، وانكسر على قارئ المصنع.
قارئ المصنع.
تصويب العداد
لقد أخبرت بشكل كامل جميع فيزياء العملية ومبدأ التشغيل في مقالتي السابقة ، لذلك أوصي بشدة أن تتعرف عليها. ومع ذلك ، لفهم ما أفعله ، سوف أقوم بتحديث بعض النقاط قليلاً.
اسمحوا لي أن أذكركم أن EM4102 يستخدم مخطط ترميز مانشستر. عندما يتم تشكيل بروتوكول EM4102 ، يمكن أن يكون وقت الإرسال بتة واحدة 64 أو 32 أو 16 فترة من تردد الموجة الحاملة (125 كيلو هرتز).
ببساطة ، عند إرسال بت واحد ، نقوم بتغيير قيمة إما واحد إلى صفر (عند إرسال الصفر) ، أو من صفر إلى واحد (عند إرسال واحد). وفقًا لذلك ، إذا اخترنا إرسال بت واحد من المعلومات 64 فترة من تردد الموجة الحاملة ، فسنحتاج لإرسال "نصف بت" 32 فترة من تردد الموجة الحاملة. وبالتالي ، يجب أن تتغير كل عضة بمعدل:
f=125000/32 = 3906,25
وستكون فترة "نصف البتة" هذه 256 مللي ثانية.
نحتاج الآن إلى حساب المؤقت بحيث يهز ساقنا بتردد معين. لكنني أصبحت كسولًا لدرجة أنني ، عند فتح ورقة البيانات وبدأت في التثاؤب ، قررت أن أجد نوعًا من الحلول الجاهزة. واتضح أن هناك حسابات توقيت جاهزة ، فقط قم بالقيادة في بياناتك. يجتمع: آلة حاسبة الموقت لاردوينو .
نحتاج فقط إلى الضغط على تردد المؤقت البالغ 3906 هرتز ، وسنقوم على الفور بإنشاء رمز جاهز للاستخدام. حسنًا ، أليست هذه معجزة!
يرجى ملاحظة أنني أدخلت التكرار كأعداد صحيحة ، لكنه حسبه كسريًا وهو الرقم الذي نحتاجه بالضبط. حصلت على رمز تهيئة المؤقت التالي:
void setupTimer1() {
noInterrupts();
// Clear registers
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// 3906.25 Hz (16000000/((4095+1)*1))
OCR1A = 4095;
// Prescaler 1
TCCR1B |= (1 << CS10);
// Output Compare Match A Interrupt Enable
TIMSK1 |= (1 << OCIE1A);
interrupts();
}
رائعة وبسيطة وموجزة.
متجه المقاطعة للإخراج بسيط جدًا أيضًا. دعني أذكرك أننا بحاجة إلى الانتقال من واحد إلى صفر في حالة تحويل الصفر ، ومن صفر إلى واحد ، في حالة تحويل واحد (انظر الشكل للتفاهم). لذلك ، ننظر إلى ما نمرره الآن وأين نحن في "نصف بت" ، ونقرأ تدريجياً جميع البيانات من مصفوفة البيانات.
ISR(TIMER1_COMPA_vect) {
TCNT1=0;
if (((data[byte_counter] << bit_counter)&0x80)==0x00) {
if (half==0) digitalWrite(ANTENNA, LOW);
if (half==1) digitalWrite(ANTENNA, HIGH);
}
else {
if (half==0) digitalWrite(ANTENNA, HIGH);
if (half==1) digitalWrite(ANTENNA, LOW);
}
half++;
if (half==2) {
half=0;
bit_counter++;
if (bit_counter==8) {
bit_counter=0;
byte_counter=(byte_counter+1)%8;
}
}
}
ترجمة البيانات للإرسال
هنا أيضًا ، يجب عليك تحديث ذاكرة تنسيقات البيانات المخزنة على البطاقة. طريقة كتابتهم. لنأخذ مثال حي.
افترض أن لدينا بطاقة ، لكن لا يوجد قارئ. الرقم 010.48351 مكتوب على البطاقة .
بطاقة حقيقية برقم 010 ، 48351.
كيف يمكننا ترجمة هذا الرقم إلى الرقم التسلسلي المكتوب على البطاقة؟ بسيطا بما فيه الكفاية. تذكر الصيغة: نترجم جزأين من الرقم بشكل منفصل:
010d = 0xA 48351d = 0xBCDF
لذلك ، نحصل على الرقم التسلسلي: 0xABCDF. دعنا نتحقق من ذلك ، ونقرأ البطاقة بقارئ (تقرأ بتنسيق عشري) ، نحصل على رقم:
0000703711
نترجمها إلى تنسيق سداسي عشري مع أي آلة حاسبة ونحصل عليها مرة أخرى: 0xABCDF.
يبدو الأمر بسيطًا جدًا ، انتظر ، الآن عليك إجهاد عقلك. دعني أذكرك بتنسيق البيانات الموجودة على البطاقة نفسها.
سأضعها في كلمات:
- هناك تسع وحدات عنوان في البداية.
- معرّف العميل الأقل نصف بايت.
- في النهاية هناك بتات التكافؤ.
- النصف الثاني من البايت هو معرف العميل.
- بت التكافؤ.
- أقل نصف بايت من البايت الصفري للرقم التسلسلي.
- بت التكافؤ
- .
- ,
- . 10 ( ).
- , .
في المجموع ، نحصل على 64 بت من البيانات (أي خمسة بايت!). كملاحظة جانبية ، لا يقرأ القارئ معرّف العميل ، وأنا أقبله على أنه صفر.
ما هو بت التكافؤ؟ هذا هو عدد الآحاد في الحزمة: إذا كان زوجيًا ، فإن بت التكافؤ يكون صفرًا ، وإلا فسيكون واحدًا. أسهل طريقة لحسابها هي مجرد XOR عادي.
في الواقع ، فكرت لفترة طويلة في كيفية جعل تحويل الرقم التسلسلي إلى حزمة أكثر أناقة ، بحيث يشغل مساحة أقل في وحدة التحكم الدقيقة. لذلك ، قمت برسم برنامج صغير يقوم بذلك. يمكن مشاهدة البرنامج تحت الجناح.
برنامج تجريبي لترجمة المسلسل إلى بيانات
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte) \
(byte & 0x80 ? '1' : '0'), \
(byte & 0x40 ? '1' : '0'), \
(byte & 0x20 ? '1' : '0'), \
(byte & 0x10 ? '1' : '0'), \
(byte & 0x08 ? '1' : '0'), \
(byte & 0x04 ? '1' : '0'), \
(byte & 0x02 ? '1' : '0'), \
(byte & 0x01 ? '1' : '0')
#define NYBBLE_TO_BINARY_PATTERN "%c%c%c%c"
#define NYBBLE_TO_BINARY(byte) \
(byte & 0x08 ? '1' : '0'), \
(byte & 0x04 ? '1' : '0'), \
(byte & 0x02 ? '1' : '0'), \
(byte & 0x01 ? '1' : '0')
int main() {
//unsigned long long card_id = 0x00000ABCDF;
//uint64_t card_id = 0x00000ABCDF;
uint64_t card_id = (uint64_t)3604000;
uint64_t data_card_ul = 0x1FFF; //first 9 bit as 1
int32_t i;
uint8_t tmp_nybble;
uint8_t column_parity_bits = 0;
printf("card_id = 0x%lX\n", card_id);
for (i = 9; i >= 0; i--) { //5 bytes = 10 nybbles
tmp_nybble = (uint8_t) (0x0f & (card_id >> i*4));
data_card_ul = (data_card_ul << 4) | tmp_nybble;
printf("0x%02X", (int) tmp_nybble);
printf("\t"NYBBLE_TO_BINARY_PATTERN, NYBBLE_TO_BINARY(tmp_nybble));
printf("\t %d\n", (tmp_nybble >> 3 & 0x01) ^ (tmp_nybble >> 2 & 0x01) ^\
(tmp_nybble >> 1 & 0x01) ^ (tmp_nybble & 0x01));
data_card_ul = (data_card_ul << 1) | ((tmp_nybble >> 3 & 0x01) ^ (tmp_nybble >> 2 & 0x01) ^\
(tmp_nybble >> 1 & 0x01) ^ (tmp_nybble & 0x01));
column_parity_bits ^= tmp_nybble;
}
data_card_ul = (data_card_ul << 4) | column_parity_bits;
data_card_ul = (data_card_ul << 1); //1 stop bit = 0
printf("\t"NYBBLE_TO_BINARY_PATTERN"\n", NYBBLE_TO_BINARY(column_parity_bits));
printf("data_card_ul = 0x%lX\n", data_card_ul);
for (i = 7; i >= 0; i--) {
printf("0x%02X,", (int) (0xFF & (data_card_ul >> i * 8)));
}
printf("\n");
return 0;
}
أهم شيء بالنسبة لنا هو الشكل الذي ستبدو عليه بتات التكافؤ. للراحة ، قمت بعمل الإخراج على الشاشة بنفس الطريقة تمامًا كما في هذه اللوحة. نتيجة لذلك ، اتضح مثل هذا.
card_id هو الرقم التسلسلي للبطاقة (الذي تحدثنا عنه أعلاه).
العمود الأول هو nibls ، والثاني هو تمثيل بتاتهم ، والثالث هو بت التكافؤ. السطر الثالث من الأسفل هو بتات التكافؤ لجميع nibls. كما قلت ، يتم حسابها ببساطة بواسطة XOR.
بعد اختبار الحسابات والتحقق منها بصريًا ، تحققت من البيانات الناتجة في البرنامج على Arduino (السطر الأخير مخصص للإدخال في الكود). كل شيء يعمل على أكمل وجه. نتيجة لرسم هذا البرنامج ، حصلت على وظيفة إعادة الحساب الجاهزة. في السابق ، كانت حسابات الإيقاع هي برامج شخص آخر على جهاز كمبيوتر ولم يعجبني تنفيذها الوحشي. وبالتالي ، تبدو وظيفة تحويل الرقم التسلسلي إلى تنسيق الإرسال كما يلي:
#define CARD_ID 0xABCDF
uint8_t data[8];
void data_card_ul() {
uint64_t card_id = (uint64_t)CARD_ID;
uint64_t data_card_ul = (uint64_t)0x1FFF; //first 9 bit as 1
int32_t i;
uint8_t tmp_nybble;
uint8_t column_parity_bits = 0;
for (i = 9; i >= 0; i--) { //5 bytes = 10 nybbles
tmp_nybble = (uint8_t) (0x0f & (card_id >> i*4));
data_card_ul = (data_card_ul << 4) | tmp_nybble;
data_card_ul = (data_card_ul << 1) | ((tmp_nybble >> 3 & 0x01) ^ (tmp_nybble >> 2 & 0x01) ^\
(tmp_nybble >> 1 & 0x01) ^ (tmp_nybble & 0x01));
column_parity_bits ^= tmp_nybble;
}
data_card_ul = (data_card_ul << 4) | column_parity_bits;
data_card_ul = (data_card_ul << 1); //1 stop bit = 0
for (i = 0; i < 8; i++) {
data[i] = (uint8_t)(0xFF & (data_card_ul >> (7 - i) * 8));
}
}
كل شيء ، يمكنك المتابعة إلى الاختبارات الميدانية. الكود المصدري للمشروع موجود هنا .
الاختبارات
كما يقولون ، من الأفضل أن ترى مرة واحدة بدلاً من أن تقرأ ألف مرة. خاصة بالنسبة لك ، لقد سجلت فيلمًا عن عمل هذا المحاكي. أردت اختباره على أجهزة حقيقية ، ومحاولة الدخول إلى المكتب باستخدام Arduino ، لكن مع الوباء اللعين ، لا يُسمح لهم بذلك. لذلك ، يجب النظر إلى الاختبارات الشاملة على الطاولة ، في ظروف المختبر.
الاستنتاجات
آمل حقًا أن تحفز مثل هذه المقالات المبتدئين على تعلم البرمجة والإلكترونيات. وستساهم أيضًا في خروج هذا النوع من البطاقات من السوق ، باعتباره أكثر البطاقات غير محمية وغير آمنة ، حيث يمكن للأطفال الآن نسخها ومحاكاتها.
أعبر عن امتناني لـ Michal Krumnikl على صبره منذ سنوات عديدة ، عندما شرح لي على icq تشغيل مثل هذا المحاكي ، بالإضافة إلى المساعدة في تطوير الكود. بمعنى ما ، هذه هي أفكاره وتطوراته قبل 13 عامًا.
الروابط