Pendahuluan
Beberapa waktu yang lalu, saya ambil bagian dalam pengembangan perangkat yang mengharuskan penerapan kriptografi Rusia. Karena di masa depan itu harus mengesahkan keputusan ini, persyaratan tertentu diajukan untuk implementasi kriptografi. Dan sebagai salah satu opsi untuk menyederhanakan penerapan persyaratan ini, kami mempertimbangkan kemungkinan mengintegrasikan pembaca kartu pintar ke dalam perangkat atau memasang chip kartu pintar, di mana banyak skenario yang diperlukan untuk bekerja dengan informasi kunci telah diterapkan.
Sayangnya, solusi seperti itu tidak cocok untuk beberapa alasan, meskipun jika dimungkinkan untuk menggunakan kriptografi perangkat keras Rusia yang sudah jadi, ini seharusnya secara signifikan mempercepat pengembangan dan sertifikasi berikutnya dari produk akhir. Dan alasan ketidakmungkinan menggunakan token USB atau kartu pintar cukup umum: perangkat seharusnya cukup kompak (modul kecil untuk perangkat M2M atau IoT), dioperasikan terutama dalam mode bebas perawatan dan dioperasikan dalam kisaran suhu yang luas.
Pada artikel ini saya ingin berbicara tentang kemungkinan solusi untuk kasus seperti itu menggunakan chip A7001, yang terhubung ke sistem melalui antarmuka I2C.

Masalah penerapan kriptografi di PAC
Saya tidak ingin memikirkan masalah sertifikasi kriptografi. Siapa pun yang bekerja dengan ini sadar akan hal ini, tetapi sisanya tampaknya tidak membutuhkannya. Tetapi tentang beberapa poin penting masih layak untuk dikatakan.
Secara teori, seharusnya tidak ada masalah khusus dengan kriptografi. Bagaimanapun, cukup untuk mengambil salah satu pustaka kriptografi, misalnya, OpenSSL, atau yang lainnya dari banyak pustaka yang ada.
Masalah dimulai ketika solusi ini harus disertifikasi. Dan implementasi perangkat lunak murni dari kriptografi dalam firmware mengubah perangkat menjadi perangkat perlindungan informasi kriptografi lengkap, yang memerlukan studi dekat di laboratorium pengujian. Lagi pula, ketika mengembangkan solusi menggunakan kriptografi, cepat atau lambat Anda harus memikirkan hal-hal seperti skema kunci, menyimpan kunci, menghasilkan angka acak dan hal-hal halus lainnya.
Ada metode yang elegan untuk menerapkan algoritma kriptografi Rusia bersertifikat untuk beberapa solusi, yang memungkinkan kami untuk sedikit menyederhanakan proses pembuatan perangkat akhir dan mengurangi waktu pengembangan dan sertifikasi selanjutnya. Cukup dengan menanamkan kartu pintar atau chip kartu pintar ke dalam perangkat, menggunakannya sebagai semacam "akar kepercayaan", dan dengan demikian menyelesaikan sejumlah besar masalah menyakitkan yang memerlukan penelitian dan konfirmasi panjang di laboratorium pengujian.

Mikrokontroler kartu pintar dengan antarmuka I2C
Untuk menulis artikel ini, saya menggunakan chip A7001, yang terhubung ke perangkat akhir melalui bus I2C, yang tersedia di hampir semua perangkat. Chip ini disediakan oleh
Aladdin RD , yang sudah memiliki firmware terpasang yang mendukung kriptografi Rusia.
Mikrokontroler A7001AG (Mikrokontroler otentikasi aman) dibuat oleh NXP. Menurut datasheet pada chip,
A7001AG adalah mikrokontroler yang dilindungi dari akses tidak sah berdasarkan arsitektur 80C51 klasik dengan coprocessor kriptografi.
Dalam mode hemat daya, mikrokontroler mengkonsumsi 50 ΞA. Ini mendukung tegangan suplai dalam kisaran dari 1,62V hingga 5,5V dan dapat dioperasikan pada suhu dari â25 ° C hingga + 85 ° C.
Untuk berinteraksi dengan perangkat eksternal, antarmuka budak I2C digunakan dengan kecepatan hingga 100 kbit / dtk.
Mikrokontroler tersedia dalam beberapa opsi perumahan. Saya berakhir dalam format HVQFN32. Ini adalah kasus plastik berukuran 5x5x0.85 mm dengan 32 kontak dan pitch 0,5 mm.
Penampilan kasus:

Pinout-nya:

Sistem host untuk menghubungkan chip A7001
EST32 WiFi Kit 32 board
Heltec diambil sebagai tata letak sistem host dengan antarmuka I2C. Harganya kurang dari 1000 rubel, memiliki semua antarmuka kabel dan nirkabel yang diperlukan, ada konektor untuk menghubungkan baterai lithium dengan sirkuit pengisian daya, serta layar OLED 0,96 inci.

Sistem yang hampir sempurna untuk membuat prototipe berbagai perangkat IoT dan M2M, yang sudah lama ingin saya mainkan.
Papan dapat diprogram baik di lingkungan pengembangan asli maupun di Arduino IDE. Ada banyak contoh untuk bekerja dengannya. Untuk kesederhanaan, saya memilih IDE Arduino standar.
Diagram sirkuit
Diagram sirkuit untuk menghubungkan chip A7001 ditunjukkan pada gambar.
Ini sedikit berbeda dari lembar data yang direkomendasikan. Menurut deskripsi pabrikan, terminal 22 (sinyal reset RST_N) seharusnya memiliki potensi tinggi, tetapi sirkuit tidak dimulai sesuai dengan skema ini. Sebagai hasil dari "poke ilmiah", operabilitas dicapai dengan menghubungkan resistor pull-up R4 ke konduktor daya negatif.UPDATE: Seperti yang disarankan dalam komentar, skema sesuai dengan lembar data, sedangkan deskripsi output membingungkan sayaRST_N - Reset input, LOW aktif

Sirkuit dipasang di papan tempat memotong roti kecil. Sinyal daya dan I2C dihubungkan oleh empat kabel penghubung, dan modul ESP32 itu sendiri terhubung ke komputer melalui USB untuk menerima daya ke seluruh rangkaian dan mengisi firmware.

Protokol I2C Kartu Cerdas
Ketika saya pertama kali mendengar tentang menghubungkan mikrokontroler kartu pintar melalui bus I2C, mereka menjelaskan kepada saya bahwa lapisan fisik antarmuka kartu pintar (GOST R ISO / IEC 7816-3-2013) diganti dengan I2C (SMBus), dan semuanya berjalan seperti biasa kartu pintar menurut GOST R ISO / IEC 7816-4-2013 menggunakan perintah APDU.
Ternyata ini tidak sepenuhnya benar, atau lebih tepatnya tidak sama sekali. Interaksi dengan mikrokontroler di tingkat tinggi memang terjadi menggunakan perintah APDU konvensional, tetapi ada beberapa "tetapi" juga.
- Antarmuka I2C (SMBus) ru.wikipedia.org/wiki/I%C2%B2C adalah bus dengan pengalamatan slave, yang secara fundamental berbeda dari antarmuka serial UART, yang dirancang untuk berkomunikasi dua perangkat secara point-to-point dan tidak menggunakan pengalamatan . Ini berarti bahwa semua data yang dikirimkan (perintah APDU) harus "dikemas" ke dalam format data bus I2C.
- Bekerja dengan kartu pintar dimulai dengan setel ulang, biasanya dengan mematikan daya, misalnya, mengeluarkan kartu secara fisik dari pembaca kartu. Setelah reset, kartu pintar pertama kali mengirim blok data ATR (Jawab Untuk Reset), yang berisi informasi konfigurasi yang diperlukan untuk mengkonfigurasi interaksi dengan kartu pintar.
Dan chip pada bus I2C tidak terkecuali, tetapi dalam kasus di mana mikrokontroler harus disolder ke papan sirkuit cetak, mungkin tidak memiliki sirkuit daya untuk sirkuit mikro atau kontrol perangkat lunak dari output reset. Oleh karena itu, reset chip diimplementasikan, termasuk, pada tingkat perintah protokol I2C.
Masalah-masalah ini dan lainnya dibahas melalui Protokol I2C Kartu Cerdas, deskripsi yang dapat ditemukan di situs web NXP di
www.nxp.com/docs/en/supporting-information/AN12207.pdf .
Bagian perangkat lunak
Pencarian pustaka dengan implementasi
protokol Smart Card I2C Protocol tidak memberikan hasil apa pun. Oleh karena itu, saya harus memahami spesifikasi dan membuat implementasi fungsi dasar apa yang ada di tangan.
Sumber sketsa untuk Arduino IDE#include <Wire.h> #include <vector> // I2C address on chip A7001 #define ADDR_A7001 static_cast<uint16_t>(0x48) using namespace std; typedef std::vector<uint8_t> vect; //-------------------------------------------------------------------------- // Output dump data by serial port void vect_dump(const char * prefix, const vect & v, const size_t start = 0, const size_t count = 0) { if(prefix) { Serial.print(prefix); } if(v.size() < start) { Serial.println("Empty"); return; } for(size_t i=0; i < (v.size()-start) && (count == 0 || i < count); i++) { uint8_t b = v[start + i]; // Format output HEX data if(i) Serial.print(" "); if(b < 0x0F) Serial.print("0"); Serial.print(b, HEX); } Serial.println(""); } //-------------------------------------------------------------------------- // Send array bytes by I2C to address A7001 and read response result_size bytes vect sci2c_exchange(const vect data, const uint8_t result_size) { Wire.beginTransmission(ADDR_A7001); Wire.write(data.data(), data.size()); Wire.endTransmission(false); Wire.requestFrom(ADDR_A7001, result_size, true); //delay(1); vect result(result_size, 0); if(result_size >= 2) { result[0] = Wire.read(); // Data size CDB result[1] = Wire.read(); // PCB for(size_t i=2; i<result.size()-2 && Wire.available(); i++) { result[i+2] = Wire.read(); } } return result; } //-------------------------------------------------------------------------- // Read Status Code uint8_t sci2c_status(const char * msg = nullptr) { vect v = sci2c_exchange({0b0111}, 2); uint8_t status = v[1] >> 4; if(msg) { Serial.print(msg); // Prefix switch(status) { case 0b0000: Serial.println("OK (Ready)"); break; case 0b0001: Serial.println("OK (Busy)"); break; case 0b1000: Serial.println("ERROR (Exception raised)"); break; case 0b1001: Serial.println("ERROR (Over clocking)"); break; case 0b1010: Serial.println("ERROR (Unexpected Sequence)"); break; case 0b1011: Serial.println("ERROR (Invalid Data Length)"); break; case 0b1100: Serial.println("ERROR (Unexpected Command)"); break; case 0b1101: Serial.println("ERROR (Invalid EDC)"); break; default: Serial.print("ERROR (Other Exception "); Serial.print(status, BIN); Serial.println("b)"); break; } } return status; } static uint8_t apdu_master_sequence_counter = 0; // Sequence Counter Master, Master to Slave //-------------------------------------------------------------------------- // Send APDU void sci2c_apdu_send(const vect apdu) { vect_dump("C-APDU => ", apdu); vect data(2, 0); // 0x00 - Master to Slave Data Transmission command + reserve to length data.insert(data.end(), std::begin(apdu), std::end(apdu)); data[0] |= (apdu_master_sequence_counter << 4); if(++apdu_master_sequence_counter > 0b111) { apdu_master_sequence_counter = 0; } data[1] = data.size() - 2; sci2c_exchange(data, 2); delay(10); sci2c_status(""); } //-------------------------------------------------------------------------- // Receive APDU vect sci2c_apdu_recv(uint8_t result_size) { Wire.beginTransmission(ADDR_A7001); Wire.write(0b0010); // 0010b - Slave to Master Data Transmission command Wire.endTransmission(false); Wire.requestFrom(ADDR_A7001, result_size, true); vect result(result_size, 0); for(size_t i=0; i<result.size() && Wire.available(); i++) { result[i] = Wire.read(); } vect_dump("R-APDU <= ", result); return result; } //-------------------------------------------------------------------------- void setup(){ Wire.begin(); Serial.begin(9600); while (!Serial); Serial.println(""); Serial.println("Smart Card I2C Protocol Arduino demo on A7001"); Serial.println(""); sci2c_exchange({0b00001111}, 2); //The bits b0 to b5 set to 001111b indicate the Wakeup command. sci2c_status("Status Wakeup: "); sci2c_exchange({0b00001111}, 2); //The bits b0 to b5 set to 001111b indicate the Wakeup command. sci2c_status("Status Wakeup: "); // Soft Reset sci2c_exchange({0b00011111}, 2); //The bits b0 to b5 set to 011111b indicate the Soft Reset command. delay(5); // Wait at least tRSTG (time, ReSeT Guard) sci2c_status("Status SoftReset: "); // Read ATR vect ATR = sci2c_exchange({0b101111}, 29+2); //The bits b0 to b5 set to 101111b indicate the Read Answer to Reset command. sci2c_status("Status ATR: "); vect_dump("ATR: ", ATR, 2); // Parameter Exchange // The bits b0 to b5 set to 111111b of the PCB send by the master device indicate the Parameter Exchange command. // The bits b6 and b7 of the PCB send by the master device code the CDBIsm,max(Command Data Bytes Integer, Slave to Master, MAXimum) vect CDB = sci2c_exchange({0b11111111}, 2); sci2c_status("Status CDB: "); vect_dump("CDB: ", CDB, 1); // Further examples of the exchange of APDU // Exchanges APDU from exmaple chapter sci2c_apdu_send({0x00, 0xA4, 0x04, 0x04, 0x04, 0x54, 0x65, 0x73, 0x74, 0x00}); sci2c_status("Status Test send: "); sci2c_apdu_recv(3+1); // R-APDU size + 1 byte PBC sci2c_status("Status Test recv: "); // Read Card Production Life Cycle sci2c_apdu_send({0x80, 0xCA, 0x9F, 0x7F, 0x00}); sci2c_status("Status card LC send: "); sci2c_apdu_recv(0x30+1); // R-APDU size + 1 byte PBC sci2c_status("Status card LC recv: "); // Read Card Info sci2c_apdu_send({0x80, 0xCA, 0x00, 0x66, 0x00}); sci2c_status("Status card info send: "); sci2c_apdu_recv(0x51+1); // R-APDU size + 1 byte PBC sci2c_status("Status card info recv: "); // Read Key Info sci2c_apdu_send({0x80, 0xCA, 0x00, 0xE0, 0x00}); sci2c_status("Status key send: "); sci2c_apdu_recv(0x17+1); // R-APDU size + 1 byte PBC sci2c_status("Status key recv: "); // Again exchanges APDU from exmaple chapter sci2c_apdu_send({0x00, 0xA4, 0x04, 0x04, 0x04, 0x54, 0x65, 0x73, 0x74, 0x00}); sci2c_status("Status Test send: "); sci2c_apdu_recv(3+1); // R-APDU size + 1 byte PBC sci2c_status("Status Test recv: "); Serial.println("Done!\n"); } //-------------------------------------------------------------------------- void loop() { delay(100); }
Untuk bekerja dengan port I2C, saya menggunakan perpustakaan Wire standar. Saya harus mengatakan segera bahwa perpustakaan ini tidak cocok untuk implementasi penuh Protokol Kartu I2C Smart, karena itu tidak memungkinkan untuk mengontrol ACK dan NACK ketika mentransmisikan dan membaca masing-masing byte, yang diperlukan untuk mengimplementasikan penerimaan data panjang variabel yang benar dari kartu pintar.
Ya, dan contoh biasa kode Kawat tidak berfungsi pertama kali, tetapi setelah beberapa menari dengan keyboard
rebana , beberapa liter kopi, googling di Yandex dan Yandex di google, solusinya ditemukan.
Wire.write ( ); Wire.endTransmission (false); Wire.requestFrom (ADDR_A7001, 2, true);
Dilihat oleh dokumentasi perpustakaan, desain ini tidak melepaskan bus I2C setelah memanggil
endTransmission . Tetapi ternyata untuk modul berbasis ESP32 yang saya gunakan, transfer data tidak terjadi secara fisik selama panggilan ke
endTransmisi (salah) , seperti yang tertulis dalam dokumentasi untuk perpustakaan Wire, tetapi selama panggilan untuk
memintaDari (benar) , sementara sebelumnya data tersebut hanya diantrekan untuk mentransfer.
Mengingat keterbatasan seperti itu, saya harus membuat "kruk", tetapi saya benar-benar ingin meluncurkan chip A7001 tanpa menulis ulang pustaka standar. Karena itu, penanganan kesalahan protokol tidak diimplementasikan, dan juga tidak mungkin untuk menerima data dengan panjang variabel (yaitu, Anda selalu perlu menentukan jumlah byte yang tepat untuk dibaca).
Pembatasan seperti itu tidak diperbolehkan dalam sistem nyata, tetapi tidak penting untuk menunjukkan penggunaan perintah APDU ketika bekerja pada bus I2C. Oleh karena itu, jika terjadi kesalahan dalam protokol pertukaran saat bertukar data melalui port I2C, maka sakelar sakelar daya adalah milik kita.
Dengan kata lain, jika selama pengulangan percobaan ini semuanya bekerja dan tiba-tiba berhenti sebelum mencari kesalahan dalam kode, matikan daya dan nyalakan. Dengan tingkat probabilitas tinggi ini dapat memperbaiki masalah.
Contoh kode untuk bekerja dengan chip A7001
Dalam contoh, saya menggunakan beberapa fungsi pembantu:
vect_dump - output data dump dalam format HEX ke port debug;
sci2c_exchange - mengirim array data melalui I2C dan membaca jumlah byte respons yang ditentukan;
sci2c_status - baca status respons dari rangkaian mikro dan, jika perlu, tampilkan statusnya di port debug;
sci2c_apdu_send - kirim perintah APDU;
sci2c_apdu_recv - baca respons terhadap perintah APDU.
Inisialisasi microchip
Menurut uraian
Protokol Kartu I2C Smart , sebelum mulai bekerja dengan chip, tiga perintah harus dijalankan secara berurutan: Reboot (Cold atau Soft Reset), Baca ATR (Read Answer to Reset) dan Siapkan parameter pertukaran (Parameter pertukaran Perangkat Utama). Dan hanya setelah itu chip siap menerima perintah APDU.
Reset lunak
Semuanya sederhana di sini, kami mengirim perintah reboot dan menunggu waktu yang ditentukan:
sci2c_exchange ({0b00011111}, 2); delay(5);
Baca Jawaban untuk Mengatur Ulang
Membaca ATR sedikit lebih rumit, seperti Anda tidak hanya perlu mengirim perintah, tetapi juga membaca data respons. Menurut uraian protokol, ukuran maksimum dari data yang dikembalikan CDBATS, MAX (Command Data Bytes, Answer To Reset, MAXimum) dapat menjadi 29 byte.
vect ATR = sci2c_exchange({0b101111}, 29+2);
Baca data ATR:
1E 00 00 00 B8 03 11 01 05 B9 02 01 01 BA 01 01 BB 0D 41 37 30 30 31 43 47 20 32 34 32 52 31Di mana 1E adalah ukuran data yang dikembalikan (29 byte + 1 byte PCB) dan 00 adalah PCB (Protocol Control Byte), yang harus sama dengan 0 dan, dalam contoh ini, data tidak dibaca dengan benar (harus ada satu byte dari PCB, dan mereka bertiga).
Berikut ini adalah data yang disandikan dalam format TLV:
B8h -
Objek data level rendah , ukuran 3 byte (
11j 01j 05j );
B9h -
Protokol pengikat objek data , berukuran 2 byte (
01j 01j );
BAh -
Objek data lapisan yang lebih tinggi , berukuran 1 byte (
01h );
BBh -
Objek data sistem operasi , 13 byte (
41 37 30 30 31 43 47 20 32 34 32 52 31 ).
Dekripsi konfigurasi read dari chipObjek data tingkat rendah :
11h - versi
utama dan kecil dari protokol yang didukung.
Kode Deteksi Kesalahan :
01h - mendukung deteksi kesalahan dan kontrol integritas data yang dikirimkan menggunakan LRC (Longitudinal Redundancy Code).
Frame waiting integer (FWI) :
05h - penundaan maksimum antara dua perintah. Kisaran nilai dapat dari 10 ms hingga 5120 ms, standarnya adalah 5120 ms. Nilai tersebut dihitung dengan rumus T = 10ms x 2 ^ FWI. Yang dalam hal ini memberi kita penundaan 320 ms (10ms x 2 ^ 5).
Objek data yang mengikat protokol - terdiri dari dua nilai,
01 jam 01 jam , yang menyandikan protokol yang didukung dan protokol default. Nilai-nilai ini berarti dukungan untuk protokol APDU [GOST R ISO / IEC 7816-3-2013], dan, seperti yang Anda duga, protokol yang sama diinstal secara default.
Objek data lapisan yang lebih tinggi - angka
01j berarti dukungan untuk format APDU pendek dan diperpanjang.
Objek data sistem operasi adalah pengidentifikasi hingga 15 byte dalam ukuran, sebagaimana didefinisikan dalam standar [GOST R ISO / IEC 7816-4-2013]. Dalam kasus kami, ini adalah string "
A7001CG 242R1 ".
Perangkat Utama bertukar Parameter
Perintah terakhir untuk menginisialisasi pengaturan pertukaran:
vect CDB = sci2c_exchange({0b11111111}, 2); sci2c_status("Status CDB: "); vect_dump("CDB: ", CDB, 1);
Nilai kembali:
CCh - (11001100b) menurut datasheet, 4 dan 5 bit harus negasi bitwise dari bit 2 dan 3 (NNb mengkode bitbegated CDBIMS, MAX) dan, menurut nilai yang disandikan, chip mendukung ukuran perintah maksimum yang mungkin dari 252 byte CDBIMS , MAX (Command Bytes Integer Data, nilai Master to Slave, MAXimum).
Menurut uraian protokol, setelah mengeksekusi ketiga perintah ini dan dalam urutan itu, rangkaian mikro siap untuk mengeksekusi perintah APDU biasa (meskipun tampaknya berfungsi tanpa mengatur parameter pertukaran, mis. Itu sudah cukup untuk melakukan Soft Reset dan membaca ATR).
Melaksanakan Perintah APDU
Setiap siklus menjalankan perintah APDU terdiri dari langkah-langkah berikut:- Kirim APDU (perintah Master to Slave Data Transmission).
- Tunggu waktu perlindungan untuk menerima dan memproses perintah.
- Tunggu pemrosesan perintah untuk membaca status (perintah Status).
- Baca data respons (perintah Transmisi Budak ke Data Master).
Logika ini diimplementasikan dalam fungsi
sci2c_apdu_send dan
sci2c_apdu_recv , dan ada poin penting di sini: dalam format Smart Card I2C Protocol, ada counter dari perintah APDU yang dikirimkan. Penghitung ini harus mengontrol Master dan perangkat Slave dan mereka dirancang untuk mengontrol urutan data yang dikirimkan, sehingga jika terjadi kesalahan penerimaan, Anda dapat mengirim atau meminta data APDU lagi.
Contoh penerapan fungsi-fungsi ini dapat ditemukan dalam kode, dan di bawah ini hanya perintah dan data respon APDU.
Contoh dari lembar data:
C-APDU =>
00 A4 04 04 04 54 65 73 74 00 - baca file dengan nama "Test".
R-APDU <=
6A 86 - menurut datasheet, jawabannya harus
64 82 (
File atau aplikasi tidak ditemukan ), tetapi dalam kasus kami firmware diunggah ke sirkuit mikro, dan jawabannya berbeda dari contoh yang dijelaskan dalam dokumentasi.
Daur Hidup Produksi Kartu Bacaan
C-APDU =>
80 CA 9F 7F 00R-APDU <=
9F 7F 2A 47 90 51 67 47 91 12 10 38 00 53 56 00 40 39 93 73 50 48 12 53 63 00 00 00 13 13C 19 30 34 30 33 39 00 00 00 00 00 00 00 90 00Baca Baca Info Kartu
C-APDU =>
80 CA 00 66 00R-APDU <=
66 4C 73 4A 06 07 2A 86 48 86 FC 6B 01 60 0C 06 0A 2A 86 48 86 FC 6B 02 02 01 01 63 09 06 07 2A 86 48 86 FC 6B 03 64 0B 06 09 2A 86 48 86 FC 6B 04 02 55 65 0B 06 09 2B 85 10 86 48 64 02 01 03 66 0C 06 0A 2B 06 01 04 01 2A 02 6E 01 02 90 00Baca Baca Info Utama
C-APDU =>
80 CA 00 E0 00R-APDU <=
E0 12 C0 04 01 FF 80 10 C0 04 02 FF 80 10 C0 04 03 FF 80 10 90 00Kesimpulannya
Pengalaman menerapkan pertukaran tim APDU melalui antarmuka I2C sangat menarik. Saya bahkan mendapati diri saya berpikir beberapa kali bahwa saya menikmati menyelesaikan berbagai masalah dari bidang sirkuit, dan dari penyolderan biasa juga, sejak terakhir kali saya harus mengambil besi solder lebih dari 5 tahun yang lalu.
Semoga artikel ini bermanfaat dan membantu memahami mereka yang tertarik dengan topik ini. Tulis jika materi yang Anda minati. Saya akan mencoba menjawab semua pertanyaan pada artikel ini, dan jika topik menggunakan Smart Card I2C Protocol menarik, maka saya akan mencoba untuk mengungkapkannya secara lebih rinci dalam publikasi berikut.
Referensi: