Protocolo SmartCard I2C. Intercambie comandos APDU a través de la interfaz I2C

Introduccion


Hace alg√ļn tiempo, particip√© en el desarrollo de un dispositivo en el que era necesario implementar la criptograf√≠a rusa. Como en el futuro se supon√≠a que certificar√≠a esta decisi√≥n, se presentaron ciertos requisitos para la implementaci√≥n de la criptograf√≠a. Y como una de las opciones para simplificar la implementaci√≥n de estos requisitos, consideramos la posibilidad de integrar un lector de tarjetas inteligentes en el dispositivo o instalar un chip de tarjeta inteligente, en el que ya se han implementado muchos escenarios necesarios para trabajar con informaci√≥n clave.

Desafortunadamente, tal soluci√≥n no encaj√≥ por alguna raz√≥n, aunque si hubiera sido posible utilizar la criptograf√≠a de hardware rusa ya preparada, esto deber√≠a haber acelerado significativamente el desarrollo y la posterior certificaci√≥n del producto final. Y las razones de la imposibilidad de usar tokens USB o tarjetas inteligentes eran bastante comunes: el dispositivo deber√≠a haber sido bastante compacto (un peque√Īo m√≥dulo para dispositivos M2M o IoT), operado principalmente en modo libre de mantenimiento y operado en un amplio rango de temperatura.

En este artículo, quiero hablar sobre una posible solución para tal caso utilizando el chip A7001, que está conectado al sistema a través de la interfaz I2C.



Problemas de implementación de criptografía en el PAC


No quiero detenerme en los problemas de la certificaci√≥n de criptograf√≠a. Quien trabaja con esto lo sabe de todos modos, pero el resto no parece necesitarlo. Pero a√ļn vale la pena mencionar algunos puntos importantes.

En teor√≠a, no deber√≠a haber ning√ļn problema especial con la criptograf√≠a. Despu√©s de todo, es suficiente tomar una de las bibliotecas criptogr√°ficas, por ejemplo, OpenSSL, o cualquier otra de las muchas existentes.

Los problemas comienzan cuando se requiere que esta soluci√≥n est√© certificada. Y la implementaci√≥n puramente de software de criptograf√≠a en el firmware convierte el dispositivo en un dispositivo de protecci√≥n de informaci√≥n criptogr√°fica completo, que requiere un estudio minucioso en un laboratorio de pruebas. Despu√©s de todo, cuando desarrolle una soluci√≥n usando criptograf√≠a, tarde o temprano tendr√° que pensar en cosas como un esquema clave, almacenar claves, generar n√ļmeros aleatorios y otros asuntos sutiles.

Existe un m√©todo elegante para implementar algoritmos criptogr√°ficos rusos certificados para algunas soluciones, que nos permite simplificar ligeramente el proceso de creaci√≥n de dispositivos finales y reducir el tiempo para su desarrollo y posterior certificaci√≥n. Es suficiente incrustar una tarjeta inteligente o un chip de tarjeta inteligente en el dispositivo, utiliz√°ndolo como una especie de "ra√≠z de confianza", y as√≠ resolver un n√ļmero significativo de problemas dolorosos que requieren una larga investigaci√≥n y confirmaci√≥n en los laboratorios de pruebas.



Microcontrolador de tarjeta inteligente con interfaz I2C


Para escribir este artículo, utilicé el chip A7001, que se conecta al dispositivo final a través del bus I2C, que está disponible en casi cualquier dispositivo. El chip fue proporcionado por Aladdin RD , que ya tiene un firmware instalado que admite la criptografía rusa.

El microcontrolador A7001AG (Microcontrolador de autenticación segura) es fabricado por NXP. De acuerdo con la hoja de datos del chip, el A7001AG es un microcontrolador protegido contra el acceso no autorizado basado en la arquitectura clásica 80C51 con un coprocesador criptográfico.

En modo de ahorro de energ√≠a, el microcontrolador consume 50 őľA. Admite voltaje de alimentaci√≥n en el rango de 1.62V a 5.5V y puede funcionar a temperaturas de ‚ąí25 ¬į C a + 85 ¬į C.

Para interactuar con dispositivos externos, la interfaz esclava I2C se utiliza con una velocidad de hasta 100 kbit / s.

El microcontrolador está disponible en varias opciones de alojamiento. Terminé en el formato HVQFN32. Esta es una caja de plástico que mide 5x5x0.85 mm con 32 contactos y un paso de 0.5 mm.

Apariencia del caso:



Su pinout:



Sistema host para conectar el chip A7001


La placa ESP32 WiFi Kit 32 de Heltec se tom√≥ como el dise√Īo del sistema host con la interfaz I2C. Cuesta menos de 1000 rublos, tiene todas las interfaces cableadas e inal√°mbricas necesarias, hay un conector para conectar una bater√≠a de litio con un circuito de carga, as√≠ como una pantalla OLED de 0.96 pulgadas.



Un sistema casi perfecto para la creación de prototipos de varios dispositivos IoT y M2M, con los que siempre he querido jugar.

La placa se puede programar tanto en el entorno de desarrollo nativo como en el IDE de Arduino. Hay muchos ejemplos para trabajar con él. Por simplicidad, me decidí por el IDE Arduino estándar.

Diagrama de circuito


El diagrama del circuito para conectar el chip A7001 se muestra en la figura. Es ligeramente diferente de la hoja de datos recomendada. Seg√ļn la descripci√≥n del fabricante, el terminal 22 (se√Īal de reinicio RST_N) deber√≠a tener un alto potencial, pero el circuito no se inici√≥ de acuerdo con este esquema. Como resultado del "golpe cient√≠fico", la operabilidad se logr√≥ conectando una resistencia pull-up R4 al conductor de potencia negativo.

ACTUALIZACI√ďN: Como se sugiere en los comentarios, el esquema corresponde a la hoja de datos, mientras que la descripci√≥n de salida me confundi√≥
RST_N - Restablecer entrada, activo BAJO




El circuito est√° montado en una peque√Īa placa de pruebas. Las se√Īales de alimentaci√≥n e I2C est√°n conectadas por cuatro cables de conexi√≥n, y el m√≥dulo ESP32 est√° conectado a la computadora a trav√©s de USB para recibir alimentaci√≥n en todo el circuito y llenar el firmware.



Protocolo de tarjeta inteligente I2C


Cuando escuch√© por primera vez sobre la conexi√≥n de microcontroladores de tarjetas inteligentes a trav√©s del bus I2C, me explicaron que la capa f√≠sica de la interfaz de la tarjeta inteligente (GOST R ISO / IEC 7816-3-2013) fue reemplazada por I2C (SMBus), y todo lo dem√°s funcion√≥ como de costumbre. Tarjetas inteligentes seg√ļn GOST R ISO / IEC 7816-4-2013 utilizando comandos APDU.

Resultó que esto no es del todo cierto, o más bien no lo es en absoluto. La interacción con el microcontrolador a un alto nivel ocurre usando comandos APDU convencionales, pero también hubo algunos "peros".

  1. La interfaz I2C (SMBus) ru.wikipedia.org/wiki/I%C2%B2C es un bus con direccionamiento esclavo, que es fundamentalmente diferente de la interfaz serial UART, que est√° dise√Īada para comunicar dos dispositivos punto a punto y no utiliza direccionamiento . Esto significa que todos los datos transmitidos (comandos APDU) deben "empaquetarse" en el formato de datos del bus I2C.
  2. El trabajo con una tarjeta inteligente comienza con su reinicio, generalmente apagando la alimentación, por ejemplo, quitando físicamente la tarjeta del lector de tarjetas. Después del restablecimiento, la tarjeta inteligente primero envía el bloque de datos ATR (Respuesta para restablecer), que contiene la información de configuración necesaria para configurar la interacción con la tarjeta inteligente.
    Y el chip en el bus I2C no es una excepción, pero en el caso de que el microcontrolador deba soldarse a la placa de circuito impreso, es posible que no tenga un circuito de suministro de energía del microcircuito o control de software de la salida de reinicio. Por lo tanto, se implementa el reinicio del chip, incluso a nivel de los comandos del protocolo I2C.

Estos y otros problemas se resuelven en el marco del Protocolo Smart Card I2C, cuya descripción se puede encontrar en el sitio web de NXP www.nxp.com/docs/en/supporting-information/AN12207.pdf .

Parte de software


Una b√ļsqueda en la biblioteca con la implementaci√≥n del protocolo Smart Card I2C Protocol no arroj√≥ ning√ļn resultado. Por lo tanto, tuve que entender las especificaciones y hacer la implementaci√≥n de las funciones b√°sicas de lo que estaba a la mano.

Fuentes de croquis para 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); } 


Para trabajar con el puerto I2C, utilicé la biblioteca estándar de Wire. Debo decir de inmediato que esta biblioteca no es adecuada para la implementación completa del Protocolo Smart Card I2C, porque no permite controlar ACK y NACK al transmitir y leer bytes individuales, lo cual es necesario para implementar la recepción correcta de datos de longitud variable desde una tarjeta inteligente.

Sí, y los ejemplos habituales del código Wire no funcionaron la primera vez, pero después de bailar con un teclado de pandereta , varios litros de café, buscar en Google en Yandex y Yandex en Google, se encontró una solución.

 Wire.write ( ); Wire.endTransmission (false); Wire.requestFrom (ADDR_A7001, 2, true); 

A juzgar por la documentaci√≥n de la biblioteca, este dise√Īo no libera el bus I2C despu√©s de llamar a endTransmission . Pero result√≥ que para el m√≥dulo basado en ESP32 que utilic√©, la transferencia de datos no ocurre f√≠sicamente durante la llamada a endTransmission (falso) , como est√° escrito en la documentaci√≥n de la biblioteca Wire, sino durante la llamada a requestFrom (true) , mientras que los datos solo se ponen en cola antes de esto para transferir.

Dadas tales limitaciones, tuve que hacer algunas "muletas", pero realmente quer√≠a lanzar el chip A7001 sin reescribir las bibliotecas est√°ndar. Debido a esto, no se implement√≥ el manejo de errores de protocolo, y tampoco fue posible recibir datos de longitud variable (es decir, siempre debe especificar el n√ļmero exacto de bytes para leer).

Dichas restricciones no están permitidas en un sistema real, pero no son esenciales para demostrar el uso de comandos APDU cuando se trabaja en el bus I2C. Por lo tanto, si se produce un error en el protocolo de intercambio al intercambiar datos a través del puerto I2C, entonces el interruptor de encendido / apagado es nuestro.
En otras palabras, si durante la repetición de estos experimentos todo funcionó y de repente se detuvo antes de buscar un error en el código, apague y vuelva a encender. Con un alto grado de probabilidad, esto puede solucionar el problema.

Ejemplos de código para trabajar con el chip A7001


En los ejemplos, uso varias funciones de ayuda:

vect_dump : envía datos de volcado en formato HEX al puerto de depuración;
sci2c_exchange : env√≠a una matriz de datos a trav√©s de I2C y lee el n√ļmero especificado de bytes de respuesta;
sci2c_status : lee el estado de respuesta del microcircuito y, si es necesario, muestra su estado en el puerto de depuración;
sci2c_apdu_send - envía un comando APDU;
sci2c_apdu_recv - lee la respuesta al comando APDU.

Inicialización de microchip


De acuerdo con la descripción del Protocolo I2C de la tarjeta inteligente , antes de comenzar a trabajar con el chip, se deben ejecutar tres comandos secuencialmente: reiniciar (restablecimiento en frío o suave), leer ATR (leer respuesta para restablecer) y configurar parámetros de intercambio (parámetro de intercambio del dispositivo maestro). Y solo después de eso, el chip está listo para aceptar comandos APDU.

Restablecimiento parcial


Aquí todo es simple, enviamos un comando de reinicio y esperamos el tiempo establecido:

 sci2c_exchange ({0b00011111}, 2); delay(5); //      (tRSTG, time, ReSeT Guard) 

Leer respuesta para restablecer


Leer ATR es un poco m√°s complicado, ya que no solo debe enviar un comando, sino tambi√©n leer los datos de respuesta. De acuerdo con la descripci√≥n del protocolo, el tama√Īo m√°ximo de los CDBATS de datos devueltos, MAX (Bytes de datos de comando, Respuesta para restablecer, MAXimum) puede ser de 29 bytes.

 vect ATR = sci2c_exchange({0b101111}, 29+2); // 29  + 1  PCB + 1  ‚ÄĒ   vect_dump("ATR: ", ATR); 

Leer datos 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 31

Donde 1E es el tama√Īo de los datos devueltos (29 bytes + 1 byte de la PCB) y 00 es la PCB (Byte de control de protocolo), que deber√≠a ser igual a 0 y, aparentemente, en este ejemplo, los datos no se leyeron correctamente (debe haber un byte de la PCB, y hay tres de ellos).

Los siguientes son datos codificados en formato TLV:

B8h - Objeto de datos de bajo nivel , tama√Īo 3 bytes ( 11h 01h 05h );
B9h - Objeto de datos de enlace de protocolo , 2 bytes de tama√Īo ( 01h 01h );
BAh : objeto de datos de capa superior , 1 byte ( 01h ) de tama√Īo;
BBh : objeto de datos del sistema operativo , 13 bytes ( 41 37 30 30 31 43 47 20 32 34 32 52 31 ).

Descifrado de la configuración de lectura del chip
Objeto de datos de bajo nivel : 11h - versiones principales y secundarias del protocolo compatible.

Códigos de detección de errores : 01h : soporte para la detección de errores y el control de integridad de los datos transmitidos utilizando LRC (Código de redundancia longitudinal).

Entero en espera de trama (FWI) : 05h - retraso máximo entre dos comandos. El rango de valores puede ser de 10 ms a 5120ms, por defecto 5120ms. El valor se calcula mediante la fórmula T = 10ms x 2 ^ FWI. Lo que en este caso nos da un retraso de 320 ms (10ms x 2 ^ 5).

Objeto de datos de enlace de protocolo : consta de dos valores, 01h 01h , que codifican el protocolo compatible y el protocolo predeterminado. Estos valores significan soporte para el protocolo APDU [GOST R ISO / IEC 7816-3-2013], y, como puede suponer, el mismo protocolo est√° instalado de forma predeterminada.

Objeto de datos de capa superior : el n√ļmero 01h significa soporte para el formato APDU corto y extendido.

El objeto de datos del sistema operativo es un identificador de hasta 15 bytes de tama√Īo, como se define en el est√°ndar [GOST R ISO / IEC 7816-4-2013]. En nuestro caso, esta es la cadena " A7001CG 242R1 ".

Par√°metro de intercambio de dispositivos maestros


El √ļltimo comando para inicializar la configuraci√≥n de intercambio:

 vect CDB = sci2c_exchange({0b11111111}, 2); sci2c_status("Status CDB: "); vect_dump("CDB: ", CDB, 1); 

Valor de retorno: CCh - (11001100b) de acuerdo con la hoja de datos, 4 y 5 bits deben ser negaci√≥n bit a bit de los bits 2 y 3 (NNb codifica los CDBIMS negados a nivel de bit, MAX) y, de acuerdo con el valor codificado, el chip admite el tama√Īo de comando m√°ximo posible de 252 bytes CDBIMS , MAX (entero de bytes de datos de comando, maestro a esclavo, valor m√°ximo).

De acuerdo con la descripción del protocolo, después de ejecutar estos tres comandos y en ese orden, el microcircuito está listo para ejecutar los comandos APDU habituales (aunque parece funcionar sin establecer parámetros de intercambio, es decir, fue suficiente para realizar un reinicio suave y leer ATR).

Ejecutando comandos APDU


Cada ciclo de ejecución de comandos APDU consta de los siguientes pasos:

  1. Enviar APDU (comando de transmisión de datos maestro a esclavo).
  2. Espere el tiempo de protección para recibir y procesar el comando.
  3. Espere a que el procesamiento del comando lea el estado (comando de estado).
  4. Leer datos de respuesta (comando de transmisión de datos esclavo a maestro).

Esta l√≥gica se implementa en las funciones sci2c_apdu_send y sci2c_apdu_recv , y aqu√≠ hay un punto importante: en el formato del protocolo del Protocolo Smart Card I2C, hay contadores de los comandos APDU transmitidos. Estos contadores deben controlar los dispositivos maestro y esclavo y est√°n dise√Īados para controlar la secuencia de datos transmitidos, de modo que en caso de un error de recepci√≥n sea posible transmitir o solicitar datos APDU nuevamente.

Se pueden encontrar ejemplos de la implementación de estas funciones en el código, y a continuación se encuentran solo los comandos APDU y los datos de respuesta.

Ejemplo de la hoja de datos:


C-APDU => 00 A4 04 04 04 54 65 73 74 00 - lea el archivo con el nombre "Prueba".
R-APDU <= 6A 86 : seg√ļn la hoja de datos, la respuesta debe ser 64 82 ( archivo o aplicaci√≥n no encontrada ), pero en nuestro caso el firmware se carga en el microcircuito, y la respuesta difiere del ejemplo descrito en la documentaci√≥n.

Ciclo de vida de producción de tarjetas de lectura


C-APDU => 80 CA 9F 7F 00
R-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 00 13 2C 19 30 34 30 33 39 00 00 00 00 00 00 00 00 90 00

Leer Leer información de la tarjeta



C-APDU => 80 CA 00 66 00
R-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 00

Leer Leer información clave


C-APDU => 80 CA 00 E0 00
R-APDU <= E0 12 C0 04 01 FF 80 10 C0 04 02 FF 80 10 C0 04 03 FF 80 10 90 00

En conclusión


Esta experiencia de implementar el intercambio de equipos APDU a trav√©s de la interfaz I2C fue muy interesante. Incluso me encontr√© varias veces pensando que disfruto resolviendo varios problemas del campo de los circuitos, y tambi√©n de la soldadura ordinaria, desde la √ļltima vez que tuve que recoger un soldador hace m√°s de 5 a√Īos.

Espero que este art√≠culo sea √ļtil y ayude a comprender a los interesados ‚Äč‚Äčen este tema. Escribe si el material te interesa. Intentar√© responder a todas las preguntas de este art√≠culo, y si el tema del uso del Protocolo Smart Card I2C es interesante, intentar√© divulgarlo con m√°s detalle en las siguientes publicaciones.

Referencias


Source: https://habr.com/ru/post/439574/


All Articles