Guía de práctica ¿Que queremos hacer? Vamos a hacer el “Hello World” del mundo de la electrónica que no es más que prender y apagar un LED a intervalos regulares. PROYECTO LAPEGÜE 1 = Prende Dispositivo 0 = Apaga ¿Como lo vamos a hacer? Vamos a utilizar un microcontrolador como dispositivo y desarrollaremos un programa en assembler que cumpla con la lógica esperada. Debemos seleccionar: 1. 2. 3. 4. 1. Que microcontrolador vamos a usar 2. Que frecuencia de CPU va a tener 3. En que pata o pín estará conectado el Led 4. La frecuencia de encendido del led Utilizaremos el microcontrolador de ATMEL ATMega 328 por ser uno de los más utilizados hoy en día. La frecuencia será de 8 MHz elegiremos la salida en la pata 14 que corresponde al PUERTO B, particularmente el bit 0 (PB0) La frecuencia será de 1Hz y debe estar 500ms encendido y 500ms Apagado ¿Y ahora? Bueno lo primero que hay que hacer indefectiblemente es estudiar la arquitectura del dispositivo, a diferencia de la programación clásica en PCs (comúnmente arquitectura i386 o AMD64 FIJA) en la programación de uC lo que cambia es justamente la arquitectura en función del dispositivo elegido, y el desarrollo del software tiene que “machear” perfectamente con ella. En este caso el dispositivo elegido es el ATMega328 Idealmente uno debe ir al datasheet http://www.atmel.com/devices/atmega328.aspx ahí es donde están todas las especificaciones del dispositivo, en mi experiencia personal eh perdido horas y horas buscando en internet como se configura tal o cual periférico por miedo a leer el tenebroso datasheet , pero cuando lo lees te das cuenta que todo esta ahí de la forma más clara y sintética que puede ser explicado. Aunque son 555 hojas para este dispositivo no hay que leerlo todo, sino solo lo que se necesita. Datasheet ¡A desarrollar! Entorno de desarrollo de software: AVRStudio Project->New Project y seleccionar AVR ASEMBLER, poner el nombre de proyecto “lape” y en initial file poner main, luego en Location elegir la carpeta donde se creará el proyecto. NEXT-> Seleccionar AVR Simulator 2 en el lado izquierdo y ATmega328 en el derecho, en este punto estamos seleccionando el microcontrolador que vamos a utilizar (en este caso a simular). FINISH-> y listo ya tenemos nuestro proyecto creado. ¿Que es lo primero que tenemos que incluir? (no vale mirar abajo) Si, el archivo que hace la redefinición de nombre de los registros, podríamos programar esto sbi 0x25, 2 Que funcionaria bien, pero claramente es mejor escribir la sentencia de la siguiente manera sbi PORTB,2 // set bit 2 in register 0x25 (que para el ATmega328 es el puerto B ) Esto permita que el código sea mucho más portable que el primer ejemplo. Por lo tanto incluyamos el archivo de definiciones. .include <m328def.inc> loop_for_ever: rjmp loop_for_ever Una vez que agregamos esto vamos a ensamblar el código todo ok? En este punto tenemos un programa que se puede bajar al micro (aunque como se ve no hace nada) si vamos a “Project Patht”/lape se puede ver los archivos generados. Abrir el .hex con el block de notas… Continuemos ahora asignando algunos nombres a registros, que utilizaremos como variables, pero tiene que quedar bien en claro que no son variables sino registros de hardware, o sea que no están en memoria RAM. Debajo de la asignación de nombres agregamos el vector de interrupciones. Analizar la estructura y la secuencia de ejecución. .include <m328def.inc> .def Temp1 = r17 .def Temp2 = r18 .def Temp3 = r19 .def data .def byte = r20 = r21 .equ INPUT .equ OUTPUT = 0x00 ; = 0xFF ; .org 0x0000 rjmp RESET rjmp EXT_INT0 rjmp EXT_INT1 rjmp PC_INT0 rjmp PC_INT1 rjmp PC_INT2 rjmp WDT rjmp TIM2_COMPA rjmp TIM2_COMPB rjmp TIM2_OVF rjmp TIM1_CAPT rjmp TIM1_COMPA rjmp TIM1_COMPB rjmp TIM1_OVF rjmp TIM0_COMPA rjmp TIM0_COMPB rjmp TIM0_OVF rjmp SPI_STC rjmp USART_RXC rjmp USART_UDRE rjmp USART_TXC rjmp ADC_RDY rjmp EE_RDY rjmp ANA_COMP rjmp TWI rjmp SPM_RDY EXT_INT0: EXT_INT1: PC_INT0: PC_INT1: PC_INT2: WDT: TIM2_COMPA: TIM2_COMPB: TIM2_OVF: ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Reset Handler IRQ0 Handler IRQ1 Handler PCINT0 Handler PCINT1 Handler PCINT2 Handler Watchdog Timer Handler Timer2 Compare A Handler Timer2 Compare B Handler Timer2 Overflow Handler Timer1 Capture Handler Timer1 Compare A Handler Timer1 Compare B Handler Timer1 Overflow Handler Timer0 Compare A Handler Timer0 Compare B Handler Timer0 Overflow Handler SPI Transfer Complete Handler USART, RX Complete Handler USART, UDR Empty Handler USART, TX Complete Handler ADC Conversion Complete Handler EEPROM Ready Handler Analog Comparator Handler 2-wire Serial Interface Handler Store Program Memory Ready Handler TIM1_CAPT: TIM1_COMPA: TIM1_COMPB: TIM1_OVF: TIM0_COMPA: TIM0_COMPB: TIM0_OVF: SPI_STC: USART_RXC: USART_UDRE: USART_TXC: ADC_RDY: EE_RDY: ANA_COMP: TWI: SPM_RDY: reti loop_for_ever: rjmp loop_for_ever ¿Que falta? Ahora agreguemos el siguiente fragmento de código RESET: ldi out ldi out sei r16, high(RAMEND); Main program start SPH,r16 ; Set Stack Pointer to top of RAM r16, low(RAMEND) SPL,r16 ; Enable interrupts En este punto se setea donde comienza la pila de programa (STACK), y se habilitan las interrupciones. Hasta el momento tenemos lo que llamaríamos un template de programa, ahora hay que escribir la lógica. Comenzaremos escribiendo las rutinas de retardo, como no estamos utilizando interrupciones ni timers la única forma de crear un retardo es a través de espera ocupada (Busy Wait) del CPU. Para esto hay que saber principalmente 2 cosas 1) Frecuencia de CPU 2) Tiempo que tardan en ejecutarse cada instrucción del set. La frecuencia la escogemos nosotros, por ejemplo 4MHz, y para los tiempos de instrucción podemos ir al datasheet ( Instruction Set Summary) y ver cuanto tarda cada instrucción involucrada en la rutina. Un ejemplo a continuación. Comenzaremos realizando un retardo de 5 ms ;-------------------------------------------------------------;Rutinas de Retardo de 5ms ;-------------------------------------------------------------; hacer un llamado tarda 5 ciclos delay5ms: ldi Temp1, 66 ;para 8mhz ; 1 ciclo LOOP0: ldi temp2, 200 ; 1 ciclo LOOP1: dec temp2 ; 1 ciclo brne LOOP1 ; 1 si es falso 2 si es verdadero dec Temp1 ; 1 brne LOOP0 ; 2 ret ;-------------------------------------------------------------Llamada 5 Ldi 1 1 LDI 2 1 dec 1 1 brne 1 2 dec 2 1 brne 2 2 ret 4 * * * * * * * * 1 1 66 200 200 66 66 1 Tiempo= (5+1+66+(200+400)*66+66+132+4)/8000000= 0,00498seg ~= 5ms De la misma manera procedemos para hacer un retardo de 0,5 seg utilizando la rutina anterior ;-------------------------------------------------------------;Rutinas de Retardo de 500ms ;-------------------------------------------------------------delay500ms: ldi temp3,100 ;para 8mhz LOOP500ms: call delay5ms dec temp3 brne LOOP500ms ret ;-------------------------------------------------------------También podían utilizar la aplicación AVRdelayloop.exe que les hace estos cálculos automáticamente ;) . Ahora hay que inicializar el puerto escogido como salida. y por último PRENDER y APAGAR el LED RESET: ldi out ldi out ldi out Temp1, low(RAMEND) SPL, Temp1 Temp1, high(RAMEND) SPH, Temp1 temp1,0xFF DDRB,temp1 sei ;******************************************** ; Bucle Principal ;******************************************** final: call delay500ms sbi PORTB,0 call delay500ms cbi PORTB,0 rjmp final NOTA: Leer “Newbies guide to AVR development.pdf” Como último paso hay que debuggear, así que podemos hacerlo apretando movemos por el código. si queremos algo más pro Proteus!!! con F11 nos