TODOPIC
Microcontroladores PIC => Almacén del Assembler => Mensaje iniciado por: Fedextrem en 25 de Enero de 2015, 11:40:08
-
Muy buenas a todos gente, que tal? Hace un par de días estuve mirando los foros de la página y veo que hay temas variados e interesantes. Necesitaría un poco de ayuda con un programa que tengo que realizar para febrero (Para el colegio) de programación en lenguaje assembler. Utilizando el PIC16F88 se debe realizar lo siguiente: 1) Utilizando 4 llaves envíe por el puerto serie (9600,8,N,1) los caracteres ASCII en el rango de los números del 0 al 9 en forma constante. 2) Utilizando 2 displays 7 segmentos multiplexados, muestre el valor hexadecimal del ASCII enviado por el puerto serie. 3) Si desde la PC se recibe un carácter cualquiera los displays deberán parpadear a 0.5 segundos con el valor "00" durante 5 segundos, y luego continuará los puntos anteriores.
No tengo mucha dificultad entendiendo el lenguaje pero hay un par de cuestiones a las que no les agarro la mano, también por no tener mucho tiempo libre porque ademas tengo otras materias, necesitaría un poco de ayuda o consejos para encarar este programa.
Mi idea era realizar la multiplexación con TMR0 en modo temporizador y el parpadeo de los displays con TMR1, pero me dijeron que podía usar solo TMR0, en modo temporizador para el multiplexado y para el parpadeo en modo contador. En que podría ayudarme?
Desde ya gracias.
-
Empezando que realizar un programa asi en ASM no lo veo nada corto y pequeño. Por lo tanto el codigo no te lo va a dar nadie o te pueden dar codigos de la UART / displays, segundo para febrero estando el dia 25 estas muy cerca y hay muchisimas cosas para hacer
Por ejemplo:
UART - http://www.oz1bxm.dk/PIC/628uart.htm (Para un PIC16f628 pero te podes dar una guia )
Display 1 digito - http://www.todopic.com.ar/foros/index.php?topic=12345.msg82948#msg82948
En el display tendrias que hacer como dijiste una multiplexacion por lo tanto podrias usar un timer para que genere una interrupcion cada X tiempo. En esta interrupcion solo activarias el otro catodo y cambiarias el valor del puerto.
Tambien podrias usar este mismo timer ( y un contador es decir una variable que se incremente y se borre cuando llegue a cierto valor ) para hacer lo del apagado de los 0.5segundos (mas que obvio que el tiempo de interrupcion deberia ser un valor que multiplicado por un entero de 0.5s o aproximado), esto funcionaria condicionalmente si hay una flag activa ( un bit de alguna variable que tengas para el control del flujo ). O usar otro timer para este punto. O directamente no usar timer y usar demoras para la multiplexacion y timer para cuando recibe el dato..
Interrupcion por RX de la UART, asi cuando recibe un dato activa esta ultima flag y comienza a prender y apagar los displays. Al finalizar la animacion apagas el flag
Tener previsto que vas a hacer con los botones, preveer si se presionan todos o 2 al mismo tiempo, que no cuenten 2 o mas veces cuando lo presionas 1 sola ves, implementar anti-rebote por software o por hardware?
Implementar la conversion de niveles de tension para la conexión UART con la PC.
La salida del PIC la vas a hacer binaria y utilizar un CD4511 o directamente desde el PIC generar las salidas ?
Todas esas cuestiones te tenes que preguntar. Y de seguro que me estoy olvidando algunas.
también por no tener mucho tiempo libre porque ademas tengo otras materias
En fin... como dije, no es nada corto. Y solo te puedo dar consejos a no ser que vengas con un problema en particular y poseas un codigo para ver donde esta el problema.
Tiempo siempre hay que hacerlo. En la universidad/colegio realmente me la pase sin hacer nada hasta los finales. Y la idea es que aprendas y no recibir el codigo de la nada. Vas a renegar por supuesto como todos :P
-
Es más mi profesor me dijo que se le hizo corto el programa. A tu pregunta te respondo que usamos llaves osea que nos dijeron que no hace falta que le pongamos anti-rebote, acá te/les dejo un video del programa simulado en PROTEUS echo por mi profesor.
Desde el PIC genero las salidas.
-
Sinceramente yo no enviaria datos asi como lo hace tu profesor pero bueno. Tambien se nota que no le gusta inicializar las variables a 0 o tiene algo mal, lo podes ver cuando inicia el programa que le queda en "30" xD
Si es asi como lo quiere entonces si es mas corto. Ademas si queres ni siquiera usas interrupciones, directamente con delays. Y tal ves por ser simulacion NI SIQUIERA ponerle delays. Solo el de 0.5s
Como dije usa un 4511... lo cual le libera muchos mas pines y le simplifica las cosas. Tambien veo que deja de enviar datos cuando entra en la animacion asi debe ser algo asi:
org 0x00
Configuarion:
Aca configuras todo
Main:
Tomo datos de las llaves y guardo en una variable que luego usas para todo hasta que actualiza nuevamente en esta parte
Esta libre la UART ?
le sumo 0x30 o "0" (si es de 0 a 9, si es mas entonces vas a tener que ver la tabla ASCII y apartir que sea mayor a 10, le restas 10 y le sumas un "A")
lleno buffer de TX para que envie el valor
Pongo datos de llaves al port ( no BCD sino el directo de las llaves )
activo un catodo
delay ( opcional si es simulacion )
desactivo catodo
muevo datos de llaves al port
activo catodo2
delay (opcional si es simulacion )
Recibi algo (flag RX)?
Asigno un contador con el valor 10 decimal
Borro flag
Pongo a 0 port
Activo ambos catodos
Delay ( 0.5s )
Apago catodos
Decremetno variable para contar las cantidades de veces que paso por aca
Realizo lo de arriba 10 veces y continuo ( es decir aca un goto y un BTFSC o BTFSS STATUS,Z a una variable contador )
Vuelvo a main
2/3/4 variables, 2 delays distintos y lo tenes. Ahora te queda programarlo xD
El pseudocodigo que te di creo que casi casi es una traduccion directa al ASM.
Creo que lo mas complicado que te queda hacer es la condiguracion de la UART y los delays ( que hay programitas o sitios online que te lo realizan)
http://www.golovchenko.org/cgi-bin/delay
-
Cuando inicia el programa le queda en 30 porque las llaves están todas en cero, el decimal (cero) del ascii es el 30. También la configuración de la UART ya la tengo hecha.
KILLERJC vos me decís que lo haga con delays? Porque tengo un entrenador digital con el 4511. Me podrías explicar un poquito más porque tal vez se me hace más fácil de lo que pienso
-
No le puede quedar 30 por mas que sea ASCII del 0 ( cero ), sino esta mal hecho el programa, y si fuera un numero mayor o igual a 10 (4 llaves 2^4=16 ) necesitaria 4 digitos para representarlo 0x31 0x30 en ASCII. Aparte "magicamente" luego de que recibe un caracter por la USART se pone en 0 las decenas ..... imagino que es en decimal por que si es en hexa no vale la pena hacerlo con 2 displays ya que uno solo alcanzaria.
Sobre el cd4511 te aconsejo que busques el datasheet, en realidad siempre busca el datasheet de los IC e intenta ver que son o como funcionan, es muy simple solo le das el valor en binario ( 4 bits ) y este lo decodifica a 7 segmentos.
No se exactamente que explicarte. El pseudocodigo esta muy simple, tanto que puede llevar 1/2 hasta maximo 4/5 lineas de ASM ( creo que hay 2 lineas de pseudocodigo que te llevaria eso ) por cada linea de pseudocodigo.
Pero hay que diferenciar una cosa. Si lo tenes que hacer fisicamente o en simulacion, si lo haces en fisico entonces vas a necesitar un poco mas de consideracion con los delays y el antirebote, si es simulacion no te preocupes, el unico que necesitarias es el de 0.5s.
Y si lo podes hacer con delays. Asi tal cual lo hizo el .... se nota por como funciona ( deja de enviar datos cuando recibe algo y se pone a hacer la animacion de los ceros ).
¿Que necesitas que te explique?
Para la configuracion de la UART vas a tener que leerte la seccion de UART del datasheet del PIC para ver los registros involucrados de la UART y definir los valores de los registros (Ni calcular hay tablas que te dan el valor a cargar para el baud rate dentro del datasheet )
Tenes que configurar los puertos a digitales y como salidas/entradas segun sea el caso seguro que tenes cerca de 2 registros por puerto.
Listo terminaste la configuracion.
Ahora el programa... que ya te lo pase :) en pseudocodigo por supuesto.
No se me ocurre que otras dudas puedas tener. Tenes ya todo ahi, solo es agarrar el datasheet leer los capitulos que necesitas "I/O Ports", "AUSART" la parte de ASYNCHRONOUS y baud rate y "Instruction Set Sumary"
Ademas de saber setear los bits de configuracion, que eso el MPLAB X te lo genera.
Cualquier duda que tengas la explico.. Si esque la se obviamente :D
Con respecto al programa.. el programa principal se basa en lo siguiente:
Tomar datos
Enviarlos ( si es que no esta trasmitiendo )
Mostrarlos en display
Preguntar si recibiste algo para hacer la animacion
Usando el diagrama de tu profesor
1- Tomar datos:
En esta seccion tomas el dato de PORTA solo el nibble superior, asi que deberias hacer un SWAPF PORTA,W de esa forma cambia el lugar los nibbles y lo almacena en W, luego un ANDLW 0x0F y solo te queda el dato de las llaves. Este dato lo guardas en una variable supongamos A_ENVIAR
Pero necesitas saber si supera el valor 10 en decimal, por lo tanto podrias restarle 10 para saber si es mayor o menor a 10 el numero. si es mayor pones en 1 la variable decenas y el resto en unidades, si no pones a 0 decenas y copias el valor a las unidades
Ya ahi tenes 3 variables:
A_ENVIAR : Este vas a enviar por UART pronto
DECENAS: Valor del digito decenas
UNIDADES: valor digito unidades
2- Enviar datos por UART
Como el programa es mas rapido que el envio de datos pregunto primero si se esta enviando los datos o no. si se estan enviando entonces continuo con lo de mostrar en el display y no molesto a la UART
Listo hora de enviar... Pregunto si las DECENAS esta en 1.. Si estan en 1 significa que el valor es como minimo un A, entonces le sumo a UNIDADES el valor de "A" y lo guardo en W, posteriormente lo mando al buffer de TX.
Si DECENAS esta en 0, simplemente le sumo 30 a UNIDADES guardo en W y de ahi al buffer de TX
Fin
3- Mostrar display
Pongo DECENAS en PORTB
activo el comun de diplay de decenas
Delay ( si vas al fisico )
Pongo UNIDADES en PORTB
Activo el comun de display de unidades
Delay ( si vas al fisico )
Fin
4- Recepecion? :
Al recibir se activa el flag de interrupcion, como no estan activadas simplemente pregunto por ese bit. Si no recibio nada Salgo y vuelvo a comenzar todo de nuevo.
Si recibio algo entonces es hora de hacer la animacion.
Nueva Variable CONTADOR = 5 decimal.
Borro el flag de la recepcion para que no entre aca de nuevo ya que ya se que recibi algo.
Pongo a 0 el PORTB ( asi entra un 0 en el 4511 y este activa las salidas para un 0 en el display )
Vuelta:
Y activo ambos catodos ( ambos reciben el 0 por lo tanto los 2 muestran en 0 )
Delay ( 0.5s )
Apago los catodos
Delay ( 0.5s )
Decremento en 1 a CONTADOR
BTFSS STATUS,Z ( es decir CONTADOR llego a 0 ?, si llego a 0 que salte, sino se ejecuta el GOTO y prende y apaga 10 veces ( 5 * 1s = 5s ) ) Es 1s por que estan los 2 delays el de prendido y el de apagado.
GOTO Vuelta
FIN
Pensa que cada punto es un programita aparte, y el programa principal solo hace CALL a esos programitas, y el FIN vendria a ser un RETURN.
PD: Mas claro no lo podes tener.
-
Mira yo te digo como va a ser, muy posiblemente el profesor me va a pedir el .ASM para ver como lo hice y también lo quiere ver funcionar en un entrenador digital, el cual tiene displays 7 segmentos, puerto serie, 4511, leds, llaves, y un par de cosas interesantes pero que no estamos usando, en el medio de todo hay un protoboard y alrededor hay como borneras para hacer las conexiones con las diferentes cosas antes mencionadas, como es una plaqueta armada ya, lo único que tengo que hacer es programar, meterlo si o si en el PIC16F88 y mostrarlo. Otra cosa, ami me encantaría hacerlo con delays porque tenes razón es más facil, pero el profesor quiere que los hagamos con interrupciones, yo podría hacerlo como lo estamos hablando pero si me pide verlo y ve que no tiene interrupciones no le va a gustar :5]
Ya tengo la configuración de puerto serie hecha:
; ORG 0x000
; GOTO BOOTSTART
; ORG 0x004
; GOTO INTERRUPCION
ORG 0 ; STARTUP = 0000H
CLRF STATUS ; BANK 0
GOTO BOOTSTART
ORG 4
ISR GOTO 5 ;
ORG 5
;
; --------------------------------------------------
; SET UP THE PORTS TO SUIT YOUR CIRCUIT REQUIREMENTS
; --------------------------------------------------
;
BOOTSTART
BANKSEL OSCCON
MOVLW 0X60 ;RELOJ INTERNO 4MHZ
MOVWF OSCCON
BANKSEL PORTA ; BANK0
MOVLW B'11110000'
MOVWF PORTA
MOVLW B'00000011'
MOVWF PORTB
BANKSEL TRISA ; BANK1
MOVLW B'11110000'
MOVWF TRISA
MOVLW B'00100111'
MOVWF TRISB
MOVLW B'00000000'
MOVWF ANSEL ; PORTA INPUTS = DIGITAL NOT ANALOG
;
; BAUD RATE SETTINGS
;
; BAUD VALUES WITH BRGH = 0
; ((20000000/9600)/64)-1 = 32
; MOVLW D'207' ; 1200 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'103' ; 2400 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'25' ; 9600 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'12' ; 19200 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'255' ; 1200 BAUD @ 20 MHZ FOSC +1.73 ERR
; MOVLW D'129' ; 2400 BAUD @ 20 MHZ FOSC +0.16 ERR
; MOVLW D'32' ; 9600 BAUD @ 20 MHZ FOSC -1.36 ERR
; MOVLW D'15' ; 19200 BAUD @ 20 MHZ FOSC +1.73 ERR
; BAUD VALUES WITH BRGH = 1
; ((20000000/9600)/16)-1 = 32
MOVLW D'25' ; 9600 BAUD @ 20 MHZ FOSC +0.16 ERR
MOVWF SPBRG
MOVLW B'00100100' ; BRGH = 1
MOVWF TXSTA ; ENABLE ASYNC TRANSMISSION, SET BRGH
BANKSEL RCSTA ; BANK0
MOVLW B'10010000'
MOVWF RCSTA ; ENABLE ASYNC RECEPTION
MOVF RCREG,W
MOVF RCREG,W
MOVF RCREG,W ; FLUSH RECEIVE BUFFER
;
;
; ------------------------------------
; PROVIDE A SETTLING TIME FOR START UP
; ------------------------------------
;
CLRF TEMP
SETTLE
DECFSZ TEMP
GOTO SETTLE
LWAITCOM
CALL RECLOOP ; WAIT AND READ FROM RS232
MOVWF PORTA ; SHOW VALUE FOR DIAGNOSTIC PURPOSE
TXLOOP
NOP
BTFSS PIR1,TXIF ;XMIT BUFFER EMPTY?
GOTO TXLOOP ;NO, WAIT
MOVF PORTA ;muevo depende el puerto en donde conecte
MOVWF TXREG
GOTO LWAITCOM ; NO
;
; ----------------------------
; RECEIVE CHARACTER FROM RS232
; ----------------------------
; THIS ROUTINE DOES NOT RETURN UNTIL A CHARACTER IS RECEIVED.
RECLOOP
NOP
BTFSS PIR1,RCIF ; CHECK FOR RECEIVED DATA
GOTO RECLOOP
MOVF RCREG
MOVWF PORTB
RETURN
END;
-
Tendrias que hacer 2 cambios nomas.
En la seccion de interrupciones correspondiente a la UART RX activas el flag de una variable tuya.
En la seccion de interrupciones del timer activas/desactivas salidas Como dije aca realizas lo mismo que el "Mostrar display". Y te quedaria algo asi:
Interrupcion_timer:
Guardo contexto
Limpio flag de interrupcion
Flag propia activada de RX ?
SI ---> Aqui hago lo de la animacion con los 0
NO ---> Aqui muestros los digitos en los displays
Recargo TIMER con el valor que necesite
Devuelvo el contexto
Retorno de la interrupcion.
Vas a tener que aplicar contadores para hacer la animacion. te decia que el tiempo del timer que elijas para resfrescar lso displays deberia ser un valor tal que multiplicandolo por un entero te de 0.5s Ejemplo 50ms.
De esa forma para hacer un "delay" de 0.5s solo deberias tener algo que cuente 100 veces la entrada a la interrupcion. Y podes usar un numero mas chico para el refresco de los displays
Y aun mas facil todavia..
Si reemplazas los delays por un
BTFSS MIS_FLAGS,1
Y en la interrupcion de tu timer implementas contadores para solo poner a 1 los flags entonces estarias usando las interrupciones,
Ya no te puedo decir mas nada. queda en vos todo lo demas. Intenta programarlo y luego cuando tengas el programa y algo no te funcione lo vemos.
-
Hey me puse a hacer el programa y mira tengo una idea que tal vez me acorte el programa, estaba mirando el video que te mande y se me ocurrió que como se puede ver las decenas no cambian, osea que nunca llega al decimal 10 puesto que es del 0 al 9, entonces solamente le cargo un 3 a la decena, y mando el valor de las llaves a un display el cual sería el de las unidades. Que me decís?
-
Que no. Vuelvo a repetirte ese 3 esta mal. Deberia ser un 0 ( Como se puede ver aproximadamente en los 25 segundos del video )
No ves que cambien las decenas por que el numero ingresado con las llaves jamas es superior a 9
8 4 2 1
--------
1 0 0 1 = 8 + 1 = 9
0 1 1 0 = 4 + 2 = 6
De todas formas esto te acortaria unas 10 lineas aproximadas de tu programa, asi que no es mucho.
Explorando todas las posibilidades que puede haber pensado darte tu profesor:
- Mostrar el numero en hexa ( no tiene sentido tener 2 displays )
- Mostrarlo en ASCII ( Puede ser si utilizas hexa, pero en el segundo 25 del video luego de la animacion se nota que es un error y no un ASCII )
- No se me ocurre mas nada
En fin mostrarlo en sistema decimal, como dije no te quita mucho espacio, hasta podes buscar un "binary to BCD + ASM + PIC" en google y seguro que sale algo, algo como esto:
addlw .256-.10 ; C will be set if number was >= 10
BTFSC STATUS,C
addlw .6 ; ...adjust for 10..29
addlw -(.256-.10) ; Back to original value
Con el codigo ese pones el numero en W por ejemplo 0xC y en W te quedaria 0x12
es decir el nibble alto las decenas y nibble bajo las unidades, Solo funcionaria para numero pequeños es decir de 0x00 a 0x13 ( 0 a 19 decimal ) como entrada, suficiente ya que solo utilizas 4 bits ( 0x0 a 0xF) como entrada
Y como dije 4 lineas de codigo te ahorraste por no querer hacer eso.
-
"2- Enviar datos por UART
Como el programa es mas rapido que el envio de datos pregunto primero si se esta enviando los datos o no. si se estan enviando entonces continuo con lo de mostrar en el display y no molesto a la UART
Listo hora de enviar... Pregunto si las DECENAS esta en 1.. Si estan en 1 significa que el valor es como minimo un A, entonces le sumo a UNIDADES el valor de "A" y lo guardo en W, posteriormente lo mando al buffer de TX.
Si DECENAS esta en 0, simplemente le sumo 30 a UNIDADES guardo en W y de ahi al buffer de TX
Fin"
Me dijiste que pregunte si se estan enviando los datos, pero en el video todo el tiempo, el número que recibe lo manda, hace falta que haga esa pregunta igual?
-
Me dijiste que pregunte si se estan enviando los datos, pero en el video todo el tiempo, el número que recibe lo manda, hace falta que haga esa pregunta igual?
Si, pero tenes que dejar que la UART termine de enviar lo que le pusiste antes, es como si estuvieras comiendo un alfajor, tenes mitad en la boca y te mandan otro nuevo dentro ( Pero solo tenes espacio para un solo alfajor ).
Podes preguntar por el bit PIR1,TXIF para saber si termino la tansmision y al escribir un nuevo valor a transmitir en el TXREG ese flag se limpia solo cuando escribis para mandar ese nuevo dato( es decir se pone a 0 al poner algo en TXREG )
-
Mira yo te digo como va a ser, muy posiblemente el profesor me va a pedir el .ASM para ver como lo hice y también lo quiere ver funcionar en un entrenador digital, el cual tiene displays 7 segmentos, puerto serie, 4511, leds, llaves, y un par de cosas interesantes pero que no estamos usando, en el medio de todo hay un protoboard y alrededor hay como borneras para hacer las conexiones con las diferentes cosas antes mencionadas, como es una plaqueta armada ya, lo único que tengo que hacer es programar, meterlo si o si en el PIC16F88 y mostrarlo. Otra cosa, ami me encantaría hacerlo con delays porque tenes razón es más facil, pero el profesor quiere que los hagamos con interrupciones, yo podría hacerlo como lo estamos hablando pero si me pide verlo y ve que no tiene interrupciones no le va a gustar :5]
Ya tengo la configuración de puerto serie hecha:
; ORG 0x000
; GOTO BOOTSTART
; ORG 0x004
; GOTO INTERRUPCION
ORG 0 ; STARTUP = 0000H
CLRF STATUS ; BANK 0
GOTO BOOTSTART
ORG 4
ISR GOTO 5 ;
ORG 5
;
; --------------------------------------------------
; SET UP THE PORTS TO SUIT YOUR CIRCUIT REQUIREMENTS
; --------------------------------------------------
;
BOOTSTART
BANKSEL OSCCON
MOVLW 0X60 ;RELOJ INTERNO 4MHZ
MOVWF OSCCON
BANKSEL PORTA ; BANK0
MOVLW B'11110000'
MOVWF PORTA
MOVLW B'00000011'
MOVWF PORTB
BANKSEL TRISA ; BANK1
MOVLW B'11110000'
MOVWF TRISA
MOVLW B'00100111'
MOVWF TRISB
MOVLW B'00000000'
MOVWF ANSEL ; PORTA INPUTS = DIGITAL NOT ANALOG
;
; BAUD RATE SETTINGS
;
; BAUD VALUES WITH BRGH = 0
; ((20000000/9600)/64)-1 = 32
; MOVLW D'207' ; 1200 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'103' ; 2400 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'25' ; 9600 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'12' ; 19200 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'255' ; 1200 BAUD @ 20 MHZ FOSC +1.73 ERR
; MOVLW D'129' ; 2400 BAUD @ 20 MHZ FOSC +0.16 ERR
; MOVLW D'32' ; 9600 BAUD @ 20 MHZ FOSC -1.36 ERR
; MOVLW D'15' ; 19200 BAUD @ 20 MHZ FOSC +1.73 ERR
; BAUD VALUES WITH BRGH = 1
; ((20000000/9600)/16)-1 = 32
MOVLW D'25' ; 9600 BAUD @ 20 MHZ FOSC +0.16 ERR
MOVWF SPBRG
MOVLW B'00100100' ; BRGH = 1
MOVWF TXSTA ; ENABLE ASYNC TRANSMISSION, SET BRGH
BANKSEL RCSTA ; BANK0
MOVLW B'10010000'
MOVWF RCSTA ; ENABLE ASYNC RECEPTION
MOVF RCREG,W
MOVF RCREG,W
MOVF RCREG,W ; FLUSH RECEIVE BUFFER
;
;
; ------------------------------------
; PROVIDE A SETTLING TIME FOR START UP
; ------------------------------------
;
CLRF TEMP
SETTLE
DECFSZ TEMP
GOTO SETTLE
LWAITCOM
CALL RECLOOP ; WAIT AND READ FROM RS232
MOVWF PORTA ; SHOW VALUE FOR DIAGNOSTIC PURPOSE
TXLOOP
NOP
BTFSS PIR1,TXIF ;XMIT BUFFER EMPTY?
GOTO TXLOOP ;NO, WAIT
MOVF PORTA ;muevo depende el puerto en donde conecte
MOVWF TXREG
GOTO LWAITCOM ; NO
;
; ----------------------------
; RECEIVE CHARACTER FROM RS232
; ----------------------------
; THIS ROUTINE DOES NOT RETURN UNTIL A CHARACTER IS RECEIVED.
RECLOOP
NOP
BTFSS PIR1,RCIF ; CHECK FOR RECEIVED DATA
GOTO RECLOOP
MOVF RCREG
MOVWF PORTB
RETURN
END;
-
Teneme paciencia que es complicado esto de la pagina xD
Te quería mostrar que en la parte de transmisión y recepción se pregunta por el PIR1,TXIF y el PIR1, RCIF
-
No se de donde sacaste el codigo, Yo de ese codigo que pusiste solo pondria:
ORG 0x00
. GOTO BOOTSTART
ORG 0x04
GOTO INTERRUPCION
; Variables usadas: W_TEMP, STATUS_TEMP, PCLATH_TEMP y las propias de nuestra interrupcion FLAGS ( creada por mi para tener el flag de interrupcion de recepcion ) y DATO_RECIBIDO
; Codigo sacado del datasheet del PIC, salva el contenido de W, STATUS y PCLATH, realiza la interrupcion , y luego lo devuelve a su estado original para que ningun problema ocurra luego.
INTERRUPCION:
MOVWF W_TEMP ;Copy W to TEMP register
SWAPF STATUS, W ;Swap status to be saved into W
CLRF STATUS ;bank 0, regardless of current bank, Clears IRP,RP1,RP0
MOVWF STATUS_TEMP ;Save status to bank zero STATUS_TEMP register
MOVF PCLATH, W ;Only required if using page 1
MOVWF PCLATH_TEMP ;Save PCLATH into W
CLRF PCLATH ;Page zero, regardless of current page
; Aca el codigo de tu ISR
BSF FLAGS,0 ; Si solo tenes inetrrupcion de RX, sino vas a tener que preguntar que interrupcion fue y actuar de acuerdo a eso
MOVF RCREG,W
MOVWF DATO_RECIBIDO
BCF PIR1,RCIF
; Fin del codigo de ISR
MOVF PCLATH_TEMP, W ;Restore PCLATH
MOVWF PCLATH ;Move W into PCLATH
SWAPF STATUS_TEMP, W ;Swap STATUS_TEMP register into W
;(sets bank to original state)
MOVWF STATUS ;Move W into STATUS register
SWAPF W_TEMP, F ;Swap W_TEMP
SWAPF W_TEMP, W ;Swap W_TEMP into W
RETFIE
BOOTSTART
BANKSEL OSCCON
MOVLW 0X60 ;RELOJ INTERNO 4MHZ
MOVWF OSCCON
BANKSEL PORTA ; BANK0
MOVLW B'11110000'
MOVWF PORTA
MOVLW B'00000011'
MOVWF PORTB
BANKSEL TRISA ; BANK1
MOVLW B'11110000'
MOVWF TRISA
MOVLW B'00100111'
MOVWF TRISB
MOVLW B'00000000'
MOVWF ANSEL ; PORTA INPUTS = DIGITAL NOT ANALOG
;
; BAUD RATE SETTINGS
;
; BAUD VALUES WITH BRGH = 0
; ((20000000/9600)/64)-1 = 32
; MOVLW D'207' ; 1200 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'103' ; 2400 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'25' ; 9600 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'12' ; 19200 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'255' ; 1200 BAUD @ 20 MHZ FOSC +1.73 ERR
; MOVLW D'129' ; 2400 BAUD @ 20 MHZ FOSC +0.16 ERR
; MOVLW D'32' ; 9600 BAUD @ 20 MHZ FOSC -1.36 ERR
; MOVLW D'15' ; 19200 BAUD @ 20 MHZ FOSC +1.73 ERR
; BAUD VALUES WITH BRGH = 1
; ((20000000/9600)/16)-1 = 32
MOVLW D'25' ; 9600 BAUD @ 20 MHZ FOSC +0.16 ERR
MOVWF SPBRG
MOVLW B'00100100' ; BRGH = 1
MOVWF TXSTA ; ENABLE ASYNC TRANSMISSION, SET BRGH
BANKSEL RCSTA ; BANK0
MOVLW B'10010000'
MOVWF RCSTA ; ENABLE ASYNC RECEPTION
MOVF RCREG,W
MOVF RCREG,W ; FLUSH RECEIVE BUFFER
MAIN:
; Aca tu programa principal
CALL TOMAR_DATOS
CALL ENVIAR
CALL MOSTRAR_DISPLAY
CALL RECIBIR
GOTO MAIN
TOMAR_DATOS:
; Aca toomo datos lo paso a BCD y guardo en variable
RETURN
ENVIAR:
BTFSS PIR1,TXIF
RETURN
;Aca envio
RETURN
;lo mismo para todo lo demas
END
Incluso, no se por que seleccionar el oscilador interno a 4 Mhz pudiendolo hacer a 8Mhz, ademas 8Mhz da menos error a 9600 bauds, podes verlo en las tablas que hay en el datasheet del PIC.
Otra cosa que esta mal, cuando configura los baudios asume que esta a 20Mhz, pero segun como pusiste el OSCCON el reloj esta a 4Mhz.. lo cual es un valor incorrecto el cargado
Cabe aclarar que el codigo que pase no corregi nada de lo que dije antes.
-
addlw .256-.10 ; C will be set if number was >= 10
BTFSC STATUS,C
addlw .6 ; ...adjust for 10..29
addlw -(.256-.10) ; Back to original valueEste código en que parte lo meto?
-
Eso es para pasarlo de binario a un formato decimal como dije pasas de 0x0C a 0x12 ( 0001 0010 ) el nibble mayor tiene las decenas y el menor las unidades.
Yo lo usaria en la parte en que tomas los datos.
Suerte con la programacion
-
Esta parte no te entendí
Vas a tener que aplicar contadores para hacer la animacion. te decia que el tiempo del timer que elijas para resfrescar lso displays deberia ser un valor tal que multiplicandolo por un entero te de 0.5s Ejemplo 50ms.
De esa forma para hacer un "delay" de 0.5s solo deberias tener algo que cuente 100 veces la entrada a la interrupcion. Y podes usar un numero mas chico para el refresco de los displays
Y aun mas facil todavia..
Si reemplazas los delays por un
BTFSS MIS_FLAGS,1
Y en la interrupcion de tu timer implementas contadores para solo poner a 1 los flags entonces estarias usando las interrupciones
Ya estoy con el programa y quiero que lo veas y me digas que cosas estan mal, porque se que le faltan un par de cosas:;========================PLANTILLA 16F88=========================================
;_CONFIG1
;_CONFIG2
;CONFIGURATION BYTE 1 OPTIONS
;_CP_ALL ;ALL PROGRAM MEMORY PROTECTED
;_CP_OFF ;NO CODE PROTECTION
;_CCP1_RB0 CCP1 FUNCTION ON RB0
;_CCP1_RB3 CCP1 FUNCTION ON RB3
;_DEBUG_OFF IN-CIRCUIT DEBUGGER DISABLED, RB6 AND RB7 ARE GENERAL PURPOSE I/O PINS
;_DEBUG;_ON IN-CIRCUIT DEBUGGER ENABLED, RB6 AND RB7 ARE DEDICATED TO THE DEBUGGER
;_WRT_PROTECT;_OFF ;NO PROGRAM MEMORY WRITE PROTECTION
;_WRT_PROTECT_256 ;FIRST 256 PROGRAM MEMORY PROTECTED
;_WRT_PROTECT_2048 ;FIRST 2048 PROGRAM MEMORY PROTECTED
;_WRT_PROTECT_ALL ;ALL OF PROGRAM MEMORY PROTECTED
;_CPD_ON DATA EE MEMORY CODE PROTECTION ENABLED
;_CPD_OFF DATA EE MEMORY CODE PROTECTION DISABLED
;_LVP_ON LOW-VOLTAGE PROGRAMMING ENABLE ENABLED
;_LVP_OFF LOW-VOLTAGE PROGRAMMING ENABLE DISABLED
;_BODEN_ON BROWN OUT RESET DETEC ENABLED
;_BODEN_OFF BROWN OUT RESET DETEC DISABLED
;_MCLR_ON PIN FUNCTION IS MCLR
;_MCLR_OFF PIN FUNCTION IS DIGITAL INPUT, MCLR INTERNALLY TIED TO VDD
;_PWRTE_OFF 72 MSEC. TIME UP DELAY OFF
;_PWRTE_ON 72 MSEC. TIME UP DELAY ON
;_WDT_ON WATCHDOG TIMER ENABLE BIT ON
;_WDT_OFF WATCHDOG TIMER ENABLE BIT OFF
;_EXTRC_CLKOUT
;_EXTRC_IO ;HABILITA EL CLOCK EXTERNO -
;_INTRC_CLKOUT
;_INTRC_IO ;HABILITA EL OSCILADOR INTERNO - PINES RA6 Y RA7 ACTIVADOS
;_EXTCLK
;_HS_OSC HS OSCILLATOR
;_XT_OSC XT OSCILLATOR
;_LP_OSC LP OSCILLATOR
;CONFIGURATION BYTE 2 OPTIONS
;_IESO_ON INTERNAL EXTERNAL SWITCHOVER MODE ENABLED
;_IESO_OFF INTERNAL EXTERNAL SWITCHOVER MODE DISABLED
;_FCMEN_ON FAIL-SAFE CLOCK MONITOR ENABLED
;_FCMEN_OFF FAIL-SAFE CLOCK MONITOR DISABLED
;=====================================================================================
;
;=====================================================================================
; ENCABEZADOS
;=====================================================================================
LIST P=16F88, R=DEC ;ESPECIFICACIONES DEL MICRO
#INCLUDE <P16F88.INC> ; INCLUSION DE ARCHIVOS Y REGISTROS
__CONFIG _CONFIG1, _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_OFF & _MCLR_OFF & _LVP_OFF & _INTRC_IO
__CONFIG _CONFIG2, _IESO_OFF & _FCMEN_OFF
;=====================================================================================
; ETIQUETAS Y DECLARACION DE VARIABLES
; SE DEBE PONER EN CBLOCK EL INICIO DE LA MEMORIA RAM
; SEGUN EL MICROCONTROLADOR A USAR
;=====================================================================================
;SON 80 BYTES, BANCO 0
;VA DESDE LA POSICION DE MEMORIA H'20' HASTA H'6F'
CBLOCK H'20' ;SE PUEDE ESCRIBIR EN OCTAL O'23' EN BINARIO B'101' Y EN ASCII 'A'
W_TEMP, STATUS_TEMP
FSR_TEMP, PCLATH_TEMP
TEMP
A_ENVIAR
DECENAS
UNIDADES
CONTADOR
FLAG2
FLAG
FLAGS
CON_DIS
ENDC
;*** VARIABLES PARA TODOS LOS BANCOS ***
; SON 16 BYTES EN BANK0/1/2/3
;*******************************************
CBLOCK H'70' ;VAN DESDE H'70' HASTA H'7F'
;SON VARIABLES USADAS EN LA SUBRUTINA INTERRUPCION
ENDC
;BANCO 1 -- SON 80 BYTES, DESDE H'A0' HASTA H'EF'
;Y LUEGO VIENEN 16 BYTES COMPARTIDOS DESDE H'F0' HASTA H'FF'
CBLOCK H'A0'
ENDC
;BANCO 2 SON 80 BYTES, DESDE H'110' HASTA H'16F'
;Y LUEGO VIENEN 16 BYTES COMPARTIDOS DESDE H'170' HASTA H'17F'
CBLOCK H'110'
ENDC
;BANCO 3 SON 80 BYTES, DESDE H'190' HASTA H'1EF'
;Y LUEGO VIENEN 16 BYTES COMPARTIDOS DESDE H'1F0' HASTA H'1FF'
CBLOCK H'190'
ENDC
; ORG 0x000
; GOTO BOOTSTART
; ORG 0x004
; GOTO INTERRUPCION
ORG 0x00
. GOTO BOOTSTART
ORG 0x04
GOTO INTERRUPCION
INICIA
BANKSEL PORTA ; SELECT BANK OF PORTA
CLRF PORTA ; INITIALIZE PORTA BY
BANKSEL OSCCON
MOVLW 0X60 ;RELOJ INTERNO 4MHZ
MOVWF OSCCON
BANKSEL ANSEL ; SELECT BANK OF ADCON1
MOVLW 0X00 ; CONFIGURE ALL PINS
MOVWF ANSEL ; AS DIGITAL INPUTS
BANKSEL TRISA
MOVLW 0X0F ; VALUE USED TO
MOVWF TRISA ; SET RA<7:0> AS INPUTS
BANKSEL TRISB
CLRF TRISB
BANKSEL PORTA
CLRF INTCON
CALL TIMER
BANKSEL DISPLAY1
MOVLW B'00000001'
MOVWF CON_DIS
CLRF PORTB
MOVLW .0
MOVWF UNIDADES
MOVLW .0
MOVWF DECENAS
CLRF FLAG
; Variables usadas: W_TEMP, STATUS_TEMP, PCLATH_TEMP y la del ISR FLAGS ( creada por mi para tener el flag de interrupcion de recepcion )
; Codigo sacado del datasheet del PIC, salva el contenido de W, STATUS y PCLATH, realiza la interrupcion , y luego lo devuelve a su estado original para que ningun problema ocurra luego.
TIMER
BANKSEL OPTION_REG
MOVLW B'00000110'
MOVWF OPTION_REG
BCF INTCON,6 ;INT EXTERNAS
BSF INTCON,5 ;TMR0
BCF INTCON,4 ;INT RB0
BCF INTCON,3 ;INT RB4-RB7
BCF INTCON,1 ;BANDERA RB0
BCF INTCON,0 ;BANDERA RB4-RB7
BCF INTCON,2 ;BANDERA TMROIF
BSF INTCON,7 ;INT GENERAL
MOVLW D'217'
BANKSEL TMR0
MOVWF TMR0
RETURN
INTERRUPCION:
MOVWF W_TEMP ;Copy W to TEMP register
SWAPF STATUS, W ;Swap status to be saved into W
CLRF STATUS ;bank 0, regardless of current bank, Clears IRP,RP1,RP0
MOVWF STATUS_TEMP ;Save status to bank zero STATUS_TEMP register
MOVF PCLATH, W ;Only required if using page 1
MOVWF PCLATH_TEMP ;Save PCLATH into W
CLRF PCLATH ;Page zero, regardless of current page
; Aca el codigo de tu ISR
BTFSS FLAG,0
GOTO MULTI
DECFSZ FLAG2;ACA SE HACE EL CONTEO DEL TIEMPO DE DECREMENTACION
GOTO MULTI
MOVLW .78 ; TIEMPO DE PARPADEO 0.5 SEG
MOVWF FLAG2
BSF FLAGS,0 ; Si solo tenes inetrrupcion de RX, sino vas a tener que preguntar que interrupcion fue y actuar de acuerdo a eso
BCF PIR1,RCIF
BTFSS FLAGS
GOTO ANIMACION
;COMO PONGO QUE SIGA MOSTRANDO LOS DATOS DE LAS LLAVES?
ANIMACION
CLRF FLAG
MOVLW .0
MOVWF UNIDADES
MOVLW .0
MOVWF DECENAS
GOTO MULTI
MULTI
BTFSS CON_DIS,0
GOTO SALTO1
GOTO DISP1
SALTO1
BTFSS CON_DIS,1
GOTO DISP2
GOTO DISP2
DISP1
BANKSEL UNIDADES
MOVF UNIDADES,0
BANKSEL PORTB
MOVWF PORTB
MOVLW B'00000010'
MOVWF CON_DIS
BSF PORTB,7
GOTO XX
DISP2
BANKSEL DECENAS
MOVF DECENAS,0
BANKSEL PORTB
MOVWF PORTB
MOVLW B'00000001'
MOVWF CON_DIS
BSF PORTB,6
; Fin del codigo de ISR
XX
BCF INTCON,TMR0IF
MOVLW .217
MOVWF TMR0
MOVF PCLATH_TEMP, W ;Restore PCLATH;
MOVWF PCLATH ;Move W into PCLATH
SWAPF STATUS_TEMP, W ;Swap STATUS_TEMP register into W
;(sets bank to original state)
MOVWF STATUS ;Move W into STATUS register
SWAPF W_TEMP, F ;Swap W_TEMP
SWAPF W_TEMP, W ;Swap W_TEMP into W
RETFIE
BOOTSTART
BANKSEL OSCCON
MOVLW 0X60 ;RELOJ INTERNO 4MHZ
MOVWF OSCCON
BANKSEL PORTA ; BANK0
MOVLW B'11110000'
MOVWF PORTA
MOVLW B'00000011'
MOVWF PORTB
BANKSEL TRISA ; BANK1
MOVLW B'11110000'
MOVWF TRISA
MOVLW B'00100111'
MOVWF TRISB
MOVLW B'00000000'
MOVWF ANSEL ; PORTA INPUTS = DIGITAL NOT ANALOG
;
; BAUD RATE SETTINGS
;
; BAUD VALUES WITH BRGH = 0
; ((20000000/9600)/64)-1 = 32
; MOVLW D'207' ; 1200 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'103' ; 2400 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'25' ; 9600 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'12' ; 19200 BAUD @ 16 MHZ FOSC +0.16 ERR
; MOVLW D'255' ; 1200 BAUD @ 20 MHZ FOSC +1.73 ERR
; MOVLW D'129' ; 2400 BAUD @ 20 MHZ FOSC +0.16 ERR
; MOVLW D'32' ; 9600 BAUD @ 20 MHZ FOSC -1.36 ERR
; MOVLW D'15' ; 19200 BAUD @ 20 MHZ FOSC +1.73 ERR
; BAUD VALUES WITH BRGH = 1
; ((20000000/9600)/16)-1 = 32
MOVLW D'25' ; 9600 BAUD @ 20 MHZ FOSC +0.16 ERR
MOVWF SPBRG
MOVLW B'00100100' ; BRGH = 1
MOVWF TXSTA ; ENABLE ASYNC TRANSMISSION, SET BRGH
BANKSEL RCSTA ; BANK0
MOVLW B'10010000'
MOVWF RCSTA ; ENABLE ASYNC RECEPTION
MOVF RCREG,W
MOVF RCREG,W ; FLUSH RECEIVE BUFFER
MAIN:
; Aca tu programa principal
CALL TOMAR_DATOS
CALL ENVIAR
CALL MOSTRAR_DISPLAY
CALL RECIBIR
GOTO MAIN
TOMAR_DATOS:
addlw .256-.10 ; C will be set if number was >= 10
BTFSC STATUS,C
addlw .6 ; ...adjust for 10..29
addlw -(.256-.10) ; Back to original value
SWAPF PORTA,W ; Aca toomo datos lo paso a BCD y guardo en variable
ANDLW 0x0F
MOVF A_ENVIAR
SUBWF A_ENVIAR,0
;COMO SE SI ES MAYOR O MENOR A 10?
RETURN
ENVIAR:
BTFSS PIR1,TXIF
RETURN
BTFSC DECENAS
ADDLW UNIDADES
GOTO DEC1
BTFSS DECENAS
GOTO MUEVO
NOP
GOTO DEC1
MUEVO
MOVLW .30
MOVWF UNIDADES
DEC1
MOVF PORTA ;muevo depende el puerto en donde conecte
MOVWF TXREG ;Aca envio
RETURN
;lo mismo para todo lo demas
END En si la primer parte de configuraciones es una tabla que nos dieron en el colegio
-
mire rapidamente el codigo aunque me marie de tantos comments entre otras cosas.
Voy a hacer un codigo rapido para que entiendas lo que digo.
org 0x00
NOP
NOP
GOTO configuracion
org 0x04
GOTO interrupcion
configuracion:
; Aca configuro todo (puertos, timers, inicializo todas las variables a 00 o el valor que tengan, cargo valor de timer y activo interrupciones al ultimo )
GOTO main
;******COMIENZO DE INTERRUPCION********
interrupcion:
;guardo contexto
BTFSC PIR1,RCIF ; Pregunto por si llego algo en la UART
GOTO int_uart
BTFSS INTCON,TMR0IF ; Pregunto si la interrupcion fue del timer
GOTO salir_int ; Aca podria seguir agregando por eso pregunte arriba, y puse el GOTO, sino no hacia flata pregunta si era lo unico 2 que estaba activo
DECF CONTADOR_DISPLAYS ;Implemento contador por si es muy corta la interrupcion y para hacerla mas larga ( es decir contar la cantidad de interrupciones )
BTFSS STATUS,Z
GOTO salir_int
BSF FLAG,1 ; mi flag al pasar X veces por aca habilitando que se muestre
MOVLW 0xXX ; Recargo mi temporizador
MOVWF TMR0
MOVLW 0xXX ; Recargo mi contador
MOVWF CONTADOR_DISPLAYS
BCF INTCON,TMR0IF
GOTO salir_int
int_uart
BSF FLAG,0 ; mi flag de que llego algo por la UART
MOVF RCREG ; Limpio el buffer de lectura
MOVF RCREG ; Limpio el buffer de lectura
MOVF RCREG ; Limpio el buffer de lectura, 3 por que es posible tener 2 en la FIFO + 1 en el shift register, al vaciar la FIFO se pone a 0 RCIF
salir_int
;devuelvo el contexto
RETFIE
;******FIN DE INTERRUPCION********
;******COMIENZO DE PROGRAMA PRINCIPAL********
main:
CALL tomar_datos
CALL enviar
CALL mostrar_displays
CALL animacion
GOTO main
mostrar_displays
BTFSC FLAG,2 ; Con este indico que display estoy mostrando, si esta en 1 significa que es mi 2do display
Goto Display2
Display1:
BTFSC FLAG,1 ; Espero hasta que pase el tiempo se ejecute la interrupcion y me habilite la flag sino sigo saliendo
RETURN
BCF PORTB,catodo2
MOVF dato_decenas
MOVWF PORTB ; Aca supongo que todo lo demas que no es dato esta en 0
BSF PORTB,catodo1
BSF FLAG,2 ; Activo el flag para que pase al otro display ( 2 )
BCF FLAG,1 ; Desactivo el flag del timer
RETURN
Display2:
BTFSC FLAG,1
RETURN
BCF PORTB,catodo1
MOVF dato_unidades
MOVWF PORTB ; Aca supongo que todo lo demas que no es dato esta en 0
BSF PORTB,catodo2
BCF FLAG,2 ; Activo el flag para que pase al otro display ( 1 )
BCF FLAG,1 ; Desactivo el flag del timer
RETURN
animacion:
BTFSS FLAG,0
RETURN
;Aca parecido nomas que deberia contar un poco mas ya que el otro delay es mas corto o directamente aplicar la interrupcion a este.Y el otro no hacerle un "delay"
BCF FLAG,0
RETURN
tomar_datos:
MOVF PORTA ;supongo que esta en el porta el dato en el nibble bajo
ANDLW 0x0F
MOVWF DATO_A_ENVIAR
;paso a BCD, codigo q pase antes
ANDLW 0x0F
MOVWF UNIDADES
SWAPF DATO_A_ENVIAR,W
ANDLW 0x0F
MOVWF DECENAS
RETURN
enviar:
BTFSS PIR1,TXIF
RETURN
;Aca me fijo si es de 0 a 9 asi le sumo 0x30 ('0') o si es una A a F asi le sumo una 'A' a DATO_A_ENVIAR
MOVWF TXREG ;Cargo el valor asi se envia
RETURN
Lamentablemente creo que lo termine haciendo casi todo yo.. nuevamente e.e
Tendrias que hacer 2 partes de codigo y listo, tenes terminado tu programa, al menos es de la forma que lo encararia yo, seguro que otro tiene otra perspectiva diferente y mejor.
-
Te lo agradezco mucho y si casi todo vos lo hiciste discúlpame, para que hace meses que no encaro un programa y estaba algo oxidado. Si pudiera te daría +10 xD
Lo voy a programar y simular. Luego lo subo así vez tu obra de arte :D
Gracias de nuevo!