Tabla de contenido:

AVRSH: un intérprete de comandos para Arduino / AVR: 6 pasos (con imágenes)
AVRSH: un intérprete de comandos para Arduino / AVR: 6 pasos (con imágenes)

Video: AVRSH: un intérprete de comandos para Arduino / AVR: 6 pasos (con imágenes)

Video: AVRSH: un intérprete de comandos para Arduino / AVR: 6 pasos (con imágenes)
Video: Construyendo Nuestra Propia Computadora BASIC Desde Cero Arduino ATmega1284 IT WILL BLOW YOU AWAY!!! 2024, Mes de julio
Anonim
AVRSH: un intérprete de comandos para Arduino / AVR
AVRSH: un intérprete de comandos para Arduino / AVR

¿Alguna vez quiso "iniciar sesión" en su microcontrolador AVR? ¿Alguna vez pensó que sería genial "catalogar" un registro para ver su contenido? ¿Siempre ha querido una forma de encender y apagar subsistemas periféricos individuales de su AVR o Arduino en * tiempo real *? Yo también, así que escribí el AVR Shell, un shell similar a UNIX. Es similar a UNIX porque recuerda a la cuenta de shell que compraste para ejecutar tus bots de colisión de nick de irc, además de tener uno o dos comandos en común. También tiene un sistema de archivos que se asemeja a UNIX extfs, usando una EEPROM externa, pero eso se ha convertido en un proyecto en sí mismo, así que lanzaré ese módulo por separado bajo un instructable diferente cuando esté listo para producción. Aquí hay una lista de las cosas que puede hacer actualmente con el AVR Shell:

  • Lea todos sus registros de dirección de datos (DDRn), puertos y pines en tiempo real
  • Escriba en todos sus DDRn, puertos y pines para encender motores, LED o leer sensores en tiempo real
  • Enumere todos los registros conocidos del sistema
  • Cree y almacene valores en variables definidas por el usuario respaldadas por EEPROM.
  • Cree una contraseña de root y autentíquese con ella (se usa para el acceso telnet)
  • Leer la velocidad de reloj de la CPU configurada
  • Cambie la velocidad del reloj de su CPU configurando un preescalador
  • Iniciar y detener temporizadores de 16 bits para cronometrar varias cosas
  • Encienda y / o apague los subsistemas periféricos: Convertidores analógicos a digitales (ADC), Interfaz de periféricos en serie (SPI), Interfaz de dos cables (TWI / I2C), UART / USART. Útil para cuando desee reducir el consumo de energía del microcontrolador o para habilitar determinadas funciones.
  • Escrito en C ++ con objetos reutilizables.

Este instructivo lo guiará a través de la instalación, el uso y la personalización de avrsh.

Paso 1: lo que necesitará

Lo que necesitarás
Lo que necesitarás

Este instructivo no requiere mucho, excepto que usted:

  • Tener un Arduino o ATmega328P. Otros AVR podrían funcionar, pero es posible que deba modificar el código para enumerar cualquier registro que sea exclusivo de su MCU. Los nombres solo deben coincidir con lo que se enumera en el archivo de encabezado exclusivo de su MCU. Muchos de los nombres de registro son los mismos entre los AVR, por lo que su millaje puede variar al realizar la portabilidad.
  • Tiene una forma de conectarse a la serie USART de su Arduino / AVR. El sistema se ha probado más extensamente con AVR Terminal, una aplicación de Windows que realiza una conexión en serie a través de su puerto USB o COM. Funciona con Arduinos usando la conexión USB y cualquier AVR usando el USB-BUB de Moderndevice.com. Otras opciones de terminal incluyen: Putty, minicom (Linux y FreeBSD), pantalla (Linux / FreeBSD), Hyperterminal, Teraterm. Descubrí que putty y teraterm envían algo de basura cuando se conecta, por lo que su primer comando puede ser confuso.
  • Tenga el firmware AVR Shell instalado y en ejecución, que puede descargar de estas páginas, o siempre obtenga la última versión en BattleDroids.net.

Para instalar el Terminal AVR, simplemente descomprímalo y ejecútelo. Para instalar el firmware AVR Shell, descárguelo y cargue directamente el archivo hexadecimal y conecte su terminal serial a 9600 baudios, o compílelo usted mismo con "make" y luego "make program" para cargar el hexadecimal. Tenga en cuenta que es posible que deba cambiar la configuración de AVRDUDE para reflejar su puerto COM. Nota: El atributo PROGMEM está roto en la implementación actual de AVR GCC para C ++ y este es un error conocido. Si lo compila, espere recibir muchos mensajes de advertencia que digan "advertencia: solo las variables inicializadas se pueden colocar en el área de memoria del programa". Además de ser molesto de ver, esta advertencia es inofensiva. Como C ++ en la plataforma incrustada no ocupa un lugar destacado en la lista de prioridades de AVR GCC, se desconoce cuándo se solucionará. Si revisa el código, verá dónde he hecho soluciones para reducir esta advertencia al implementar mis propias declaraciones de atributos. Descargue e instale todo lo que pueda necesitar para luego pasar la página y empecemos.

Paso 2: lectura y escritura de registros

Registros de lectura y escritura
Registros de lectura y escritura

El AVR Shell se escribió principalmente para acceder a algunos sensores que había conectado a mi AVR. Comenzó con un simple LED, luego se trasladó a sensores de luz, sensores de temperatura y, finalmente, a dos transductores ultrasónicos. avrsh puede configurar los componentes digitales de estos sensores escribiendo en los registros que los controlan. Manipulación de registros AVR mientras se ejecuta Para obtener una lista de todos los registros conocidos en su Arduino, escriba:

imprimir registros y obtendrás una copia impresa con este aspecto

Conozco los siguientes registros:

TIFR0 PORTC TIFR1 PORTD TIFR2 DDRD PCIFR DDRB EIFR DDRC EIMSK PINB EECR PINC EEDR PIND SREG EEARL GPIOR0 EEARH GPIOR1 GTCCR GPIOR2 TCCR0A TCCR0B TCNT0 OCR0A OCR0B SPCR SPDR ACSR SMCR MCUSR MCUCR SPMCSR WDTCSR CLKPR PRR OSCCAL PCICR EICRA PCMSK0 PCMSK1 TIMSK0 TIMSK1 TIMSK2 ADCL ADCH ADCSRA ADCSRB ADMUX DIDR0 DIDR1 TCCR1A TCCR1B TCCR1C TCNT1L TCNT1H ICR1L ICR1H OCR1AL OCR1AH OCR1BL OCR1BH TCCR2A TCCR2B TCNT2 OCR2A OCR2B ASSR TWBR TWSR TWAR UBR0 UBR UBR0 UCBR UBR0 UBR0 Para ver cómo se establecen los bits individuales en cualquier registro, use el comando cat o echo

gato% GPIOR0 Aquí le pido al intérprete de comandos que muestre, o haga eco, el contenido del Registro de E / S de propósito general n. ° 0. Tenga en cuenta el signo de porcentaje (%) delante del nombre del registro. Necesita esto para indicarle al shell que se trata de una palabra clave reservada que identifica un registro. La salida típica de un comando de eco se ve así

GPIOR0 (0x0) establecido en [00000000] La salida muestra el nombre del registro, el valor hexadecimal encontrado en el registro y la representación binaria del registro (mostrando cada bit como un 1 o 0). Para establecer un bit en particular en cualquier registro, use el operador "índice de" . Por ejemplo, digamos que quiero que el tercer bit sea un 1

% GPIOR0 [3] = 1 y el shell le dará una respuesta indicando su acción y el resultado

GPIOR0 (0x0) establecido en [00000000] (0x8) establecido en [00001000] No olvide el signo de porcentaje para decirle al shell que está trabajando con un registro. También tenga en cuenta que al establecer el tercer bit, son 4 bits porque nuestros AVR usan un índice de base cero. En otras palabras, contando hasta el tercer bit, cuenta 0, 1, 2, 3, que es el cuarto lugar, pero el tercer bit. Puede borrar un poco de la misma manera estableciendo un bit en cero. Al configurar bits como este, puede cambiar el funcionamiento de su AVR sobre la marcha. Por ejemplo, cambiando el valor de coincidencia del temporizador CTC que se encuentra en OCR1A. También le permite echar un vistazo a configuraciones particulares que tendría que verificar mediante programación en su código, como el valor UBBR para su velocidad en baudios. Trabajar con DDRn, PORTn y PINn Los pines de E / S también se asignan a los registros y se pueden configurar exactamente de la misma manera, pero se ha creado una sintaxis especial para trabajar con este tipo de registros. En el código, hay un proceso normal para, digamos, encender un LED u otro dispositivo que requiera un nivel digital alto o bajo. Requiere configurar el Registro de dirección de datos para indicar que el pin es para salida y luego escribir un 1 o 0 en el bit en particular en el puerto correcto. Suponiendo que tenemos un LED conectado al pin digital 13 (PB5) y queremos encenderlo, aquí le mostramos cómo hacerlo mientras su AVR se está ejecutando

establecer pin pb5 salida escribir pin pb5 alto La salida, además de poder ver cómo se enciende su LED, se vería así

root @ ATmega328p> establecer pin pb5 salida Establecer pb5 para outputroot @ ATmega328p> escribir pin pb5 alto Escribir lógica alta en pin pb5 El "root @ ATmega328p>" es el indicador del shell que indica que está listo para aceptar sus comandos. Para apagar el LED, simplemente debe escribir un bajo en el pin. Si desea leer la entrada digital desde un pin, use el comando de lectura. Usando nuestro ejemplo anterior

root @ ATmega328p> leer pin pb5Pin: pb5 es ALTO Alternativamente, simplemente repita el registro de pines que controla ese puerto de pines. Por ejemplo, si tenemos interruptores DIP conectados a los pines digitales 7 y 8 (PD7 y PD8), podría enviar el comando

echo% PIND y el shell luego mostraría el contenido de ese registro, mostrándole todos los estados de entrada / salida de los dispositivos conectados y si el estado del interruptor estaba encendido o apagado.

Paso 3: fusibles de lectura y escritura

Fusibles de lectura y escritura
Fusibles de lectura y escritura

Los fusibles son tipos especiales de registros. Controlan todo, desde la velocidad del reloj de su microcontrolador hasta los métodos de programación disponibles para la EEPROM de protección contra escritura. A veces, necesitará cambiar esta configuración, especialmente si está creando un sistema AVR independiente. No estoy seguro de que debas cambiar la configuración de tus fusibles en Arduino. Tenga cuidado con sus fusibles; puede bloquearse si los configura incorrectamente. En un instructivo anterior, demostré cómo puede leer y configurar sus fusibles usando su programador y avrdude. Aquí, le mostraré cómo leer sus fusibles en tiempo de ejecución para ver cómo su MCU realmente los configuró. Tenga en cuenta que esta no es la configuración de tiempo de compilación que obtiene de las definiciones, sino los fusibles reales a medida que la MCU los lee en tiempo de ejecución. De la Tabla 27-9 en la hoja de datos ATmega328P (libro de datos, más parecido), los bits del Fuse Low Byte son los siguientes:

CKDIV8 CKOUT SUT1 SUT0 CKSEL3 CKSEL2 CKSEL1 CKSEL0Algo interesante a tener en cuenta es que con los fusibles, 0 significa programado y 1 significa que ese bit en particular no está programado. Algo contrario a la intuición, pero una vez que lo sabes, lo sabes.

  • CKDIV8 configura el reloj de su CPU para dividirlo por 8. El ATmega328P viene programado de fábrica para usar su oscilador interno a 8MHz con CKDIV8 programado (es decir, establecido en 0), lo que le da una frecuencia de CPU o F_CPU final de 1MHz. En Arduino, esto se cambia ya que están configurados para usar un oscilador externo a 16MHz.
  • CKOUT cuando se programa, dará salida a su reloj de CPU en PB0, que es el pin digital 8 en Arduinos.
  • SUT [1..0] especifica el tiempo de inicio de su AVR.
  • CKSEL [3..0] establece la fuente de reloj, como el oscilador RC interno, el oscilador externo, etc.

Cuando lea sus fusibles, se le devolverá en hexadecimal. Este es el formato que necesita si desea escribir los fusibles a través de avrdude. En mi arduino, esto es lo que obtengo cuando leo el byte de fusible inferior:

root @ ATmega328p> leer lfuseLower Fuse: 0xffEntonces, todos los bits se establecen en 1. Hice el mismo procedimiento en un clon de Arduino y obtuve el mismo valor. Al verificar uno de mis sistemas AVR independientes, obtuve 0xDA, que es el valor que había establecido hace algún tiempo al configurar el chip. El mismo procedimiento se usa para verificar los fusibles High Fuse Byte, Extended Fuse Byte y Lock. Los bytes de fusibles de calibración y firma se han desactivado en el código con una directiva de preprocesador #if 0, que puede cambiar si se siente mal.

Paso 4: otros comandos

Otros comandos
Otros comandos

Hay varios otros comandos que el intérprete de comandos predeterminado entiende y que pueden resultarle útiles. Puede ver todos los comandos implementados y de versiones futuras emitiendo ayuda o menú en el indicador. Los cubriré rápidamente aquí, ya que en su mayoría se explican por sí mismos. Configuración de la frecuencia del reloj de la CPU Puede averiguar qué firmware se ha configurado para usar como configuración del reloj de la CPU con el comando fcpu:

root @ ATmega328p> fcpuCPU Freq: 16000000Eso es 16 millones, o 16 millones de herz, más comúnmente conocido como 16 MHz. Puede cambiar esto sobre la marcha, por cualquier motivo, con el comando del reloj. Este comando tiene un argumento: el preescalador que se usa al dividir la velocidad del reloj. El comando clock comprende estos valores de preescalador:

  • ckdiv2
  • ckdiv4
  • ckdiv8
  • ckdiv16
  • ckdiv32
  • ckdiv64
  • ckdiv128
  • ckdiv256

Usando el comando:

reloj ckdiv2 cuando la velocidad de la CPU es de 16 MHz, la velocidad del reloj se cambiará a 8 MHz. El uso de un preescalador de ckdiv64 con una velocidad de reloj inicial de 16MHz dará como resultado una velocidad de reloj final de 250 KHz. ¿Por qué demonios querrías hacer tu MCU más lento? Bueno, por un lado, una velocidad de reloj más baja consume menos energía y si su MCU se está quedando sin batería en un gabinete de proyecto, es posible que no necesite que funcione a la velocidad máxima y, por lo tanto, podría disminuir la velocidad y reducir su consumo de energía., aumentando la duración de la batería. Además, si está utilizando el reloj para cualquier tipo de problemas de sincronización con otra MCU, por ejemplo, implementando un software UART o algo así, es posible que desee configurarlo en un valor particular que sea fácil de obtener una buena velocidad uniforme en baudios con menores tasas de error. Encendido y apagado de subsistemas periféricos En la misma nota que la reducción del consumo de energía mencionado anteriormente, es posible que desee reducir aún más la energía apagando algunos de los periféricos integrados que no está utilizando. El intérprete de comandos y el shell pueden encender y apagar los siguientes periféricos:

  • Convertidor de analógico a digital (ADC). Este periférico se utiliza cuando tiene un sensor analógico que proporciona datos (como temperatura, luz, aceleración, etc.) y necesita convertirlo a un valor digital.
  • Interfaz de periféricos en serie (SPI). El bus SPI se usa para comunicarse con otros dispositivos habilitados para SPI, como memorias externas, controladores LED, ADC externos, etc. Partes del SPI se usan para la programación del ISP, o al menos los pines, así que tenga cuidado al apagarlo. si está programando a través de ISP.
  • Interfaz de dos cables. Algunos dispositivos externos usan el bus I2C para comunicarse, aunque estos están siendo reemplazados rápidamente por dispositivos habilitados para SPI ya que SPI tiene un mayor rendimiento.
  • USART. Esta es su interfaz serial. ¡Probablemente no desee apagar esto si está conectado al AVR a través de la conexión en serie! Sin embargo, agregué esto aquí como un esqueleto para transferir a dispositivos que tienen múltiples USART como ATmega162 o ATmega644P.
  • todos. Este argumento para el comando de encendido o apagado enciende todos los periféricos mencionados o los apaga todos con un solo comando. Nuevamente, use este comando con prudencia.

root @ ATmega328p> powerdown twiPowerdown de twi complete.root@ATmega328p> powerup twiPowerup de twi complete.

Temporizadores de inicio y parada El shell tiene un temporizador de 16 bits incorporado que está disponible para su uso. Comienzas el temporizador con el comando del temporizador:

inicio del temporizadory detenga el temporizador con el argumento de parada

parada del temporizadorEste temporizador no entrará en conflicto con el temporizador USART interno. Consulte el código para conocer los detalles de implementación del temporizador USART, si ese tipo de detalle sangriento le interesa

root @ ATmega328p> inicio del temporizador Inicio temporizador.root@ATmega328p> parada del temporizador Tiempo transcurrido: ~ 157 segundos Autenticación El shell puede almacenar una contraseña de 8 caracteres en EEPROM. Este mecanismo de contraseña se creó para admitir las capacidades de inicio de sesión de telnet, pero podría ampliarse para proteger otras cosas. Por ejemplo, podría requerir ciertos comandos, como cambiar los valores de registro, a través del mecanismo de autenticación. Establezca la contraseña con el comando de contraseña.

root @ ATmega328p> passwd blah Escribe la contraseña de root en EEPROMAutorizar contra la contraseña (o requerir autorización mediante programación a través del código) con el comando auth. Tenga en cuenta que si intenta cambiar la contraseña de root y ya existe una contraseña de root establecida, debe autorizarse contra la contraseña anterior antes de poder cambiarla por una nueva.

root @ ATmega328p> passwd blinky Debes autorizarte a ti mismo primero.root@ATmega328p> auth blahAuthorized.root@ATmega328p> passwd blinky Escribe una NUEVA contraseña de root en EEPROMPor supuesto, necesitará cargar el archivo avrsh.eep si borra el firmware para restaurar sus valores y variables anteriores. El Makefile creará el archivo EEPROM por usted. Variables El shell comprende la noción de variables definidas por el usuario. El código limita esto a 20, pero puede cambiar eso si lo desea cambiando la definición de MAX_VARIABLES en script.h. Puede guardar cualquier valor de 16 bits (es decir, cualquier número hasta 65, 536) en una variable para recuperarla más tarde. La sintaxis es similar a la de los registros, excepto que se usa un signo de dólar ($) para indicar variables en el shell. Enumere todas sus variables con el comando de variables de impresión

Imprimir variables Variables definidas por el usuario: Nombre de índice -> Valor (01): $ GRATIS $ -> 0 (02): $ GRATIS $ -> 0 (03): $ GRATIS $ -> 0 (04): $ GRATIS $ -> 0 (05): $ GRATIS $ -> 0 (06): $ GRATIS $ -> 0 (07): $ GRATIS $ -> 0 (08): $ GRATIS $ -> 0 (09): $ GRATIS $ -> 0 (10): $ GRATIS $ -> 0 (11): $ GRATIS $ -> 0 (12): $ GRATIS $ -> 0 (13): $ GRATIS $ -> 0 (14): $ GRATIS $ -> 0 (15): $ GRATIS $ -> 0 (16): $ GRATIS $ -> 0 (17): $ GRATIS $ -> 0 (18): $ GRATIS $ -> 0 (19): $ GRATIS $ -> 0 (20): $ GRATIS $ -> 0 Completo. Establecer una variable

$ newvar = 25 $ tiempo de espera = 23245Obtener el valor de una variable dada

root @ ATmega328p> echo $ newvar $ newvar 25Puede ver todas las variables que ha instanciado actualmente con el comando de impresión que ya conoce

Variables definidas por el usuario: Nombre de índice -> Valor (01): newvar -> 25 (02): tiempo de espera -> 23245 (03): $ FREE $ -> 0 (04): $ FREE $ -> 0 (05): $ GRATIS $ -> 0 (06): $ GRATIS $ -> 0 (07): $ GRATIS $ -> 0 (08): $ GRATIS $ -> 0 (09): $ GRATIS $ -> 0 (10): $ GRATIS $ -> 0 (11): $ GRATIS $ -> 0 (12): $ GRATIS $ -> 0 (13): $ GRATIS $ -> 0 (14): $ GRATIS $ -> 0 (15): $ GRATIS $ -> 0 (16): $ GRATIS $ -> 0 (17): $ GRATIS $ -> 0 (18): $ GRATIS $ -> 0 (19): $ GRATIS $ -> 0 (20): $ GRATIS $ -> 0 Completo. El nombre $ FREE $ solo indica que la ubicación de la variable es gratuita y aún no se le ha asignado un nombre de variable.

Paso 5: personalización de la carcasa

Personalización de la carcasa
Personalización de la carcasa

Puede piratear el código y personalizarlo según sus propias necesidades, si lo desea. Si hubiera sabido que lanzaría este código, habría creado una clase de intérprete de comandos y una estructura de comandos separadas y simplemente habría iterado a través de esto llamando a un puntero de función. Reduciría la cantidad de código, pero tal como está el shell analiza la línea de comandos y llama al método de shell apropiado. Para agregar sus propios comandos personalizados, haga lo siguiente: 1. Agregue su comando a la lista de análisis El analizador de comandos analizar la línea de comando y darle el comando y los argumentos por separado. Los argumentos se pasan como punteros a punteros, o una matriz de punteros, como quiera trabajar con ellos. Esto se encuentra en shell.cpp. Abra shell.cpp y busque el método ExecCmd de la clase AVRShell. Es posible que desee agregar el comando a la memoria del programa. Si lo hace, agregue el comando en progmem.hy progmem.cpp. Puede agregar el comando a la memoria del programa directamente usando la macro PSTR (), pero generará otra advertencia del tipo mencionado anteriormente. Nuevamente, este es un error conocido que funciona con C ++, pero puede solucionarlo agregando el comando directamente en los archivos progmem. *, Como lo he hecho yo. Si no le importa agregar a su uso de SRAM, puede agregar el comando como lo he ilustrado con el comando "reloj". Supongamos que desea agregar un nuevo comando llamado "newcmd". Vaya a AVRShell:: ExecCmd y busque un lugar conveniente para insertar el siguiente código:

else if (! strcmp (c, "nuevocmd")) cmdNewCmd (args);Esto agregará su comando y llamará al método cmdNewCmd que escribirá en el siguiente paso. 2. Escriba su código de comando personalizado En el mismo archivo, agregue su código de comando personalizado. Esta es la definición del método. Aún querrá agregar la declaración a shell.h. Solo agréguelo a los otros comandos. En el ejemplo anterior, el código podría verse así

voidAVRShell:: cmdNewCmd (char ** args) {sprintf_P (buff, PSTR ("Tu comando es% s / r / n", args [0]); WriteRAM (buff);}Aquí hay varias cosas. Primero, "buff" es un búfer de matriz de 40 caracteres proporcionado en el código para su uso. Usamos la versión de memoria del programa de sprintf ya que le estamos pasando un PSTR. Puede usar la versión normal si lo desea, pero asegúrese de no pasar el formato en un PSTR. Además, los argumentos están en la matriz de argumentos. Si escribió "newcmd arg1 arg2", puede obtener estos argumentos con los subíndices args [0] y args [1]. Puede pasar un máximo de MAX_ARGS argumentos, como se define en el código. Siéntase libre de cambiar ese valor cuando vuelva a compilar si necesita que se pasen muchos más argumentos a la vez. WriteLine y WriteRAM son funciones globales que devuelven los métodos UART del mismo nombre. El segundo argumento de esta función está implícito. Si no pasa nada, se escribirá un símbolo del sistema después. Si pasa un 0 como segundo argumento, no se escribirá un mensaje. Esto es útil cuando desea escribir varias cadenas independientes en la salida antes de que se devuelva el símbolo del sistema al usuario. 3. Haga que el shell ejecute el código de comando. Ya le ha dicho al ejecutor del shell que ejecute el método cmdNewCmd cuando configure el nuevo comando, pero agréguelo al archivo shell.h para que el objeto shell lo entienda. Simplemente agréguelo debajo del último comando o delante del primer comando, o en cualquier lugar allí. Vuelva a compilar y cargue el firmware en su Arduino y su nuevo comando estará disponible desde el shell en el indicador.

Paso 6: Resumen

Debe saber cómo instalar y conectarse a su AVR / Arduino y obtener un mensaje en vivo en su microcontrolador en ejecución. Conoce varios comandos que extraerán datos en tiempo de ejecución de la MCU o establecerán valores en la MCU sobre la marcha. También se le ha mostrado cómo agregar su propio código personalizado para crear sus propios comandos únicos en el shell para personalizarlo aún más para sus propias necesidades. Incluso puede destripar el intérprete de comandos para que solo contenga sus comandos personalizados, si eso se adapta a sus necesidades. Espero que haya disfrutado de este instructivo y que el AVR Shell pueda ser útil para usted, ya sea como intérprete de comandos en tiempo real o como un proceso de aprendizaje para implementar el suyo. Como siempre, espero cualquier comentario o sugerencia sobre cómo se puede mejorar este instructivo. ¡Diviértase con su AVR!

Recomendado: