Lo primero es acerca de las instrucciones en ASM, en que caso es recomendable usarlas.
Unida a la pregunta anterior,¿por que no recomendarias usar ASM en XC8? ¿En otros compiladores tampoco lo recomendarías o no?
ASM unicamente lo usarias en caso de que quieras hacer algo muy especifico o si quisieras tener un mayor control sobre como se hace, o reducir los tiempos al maximo, cuando me refiero a control, es a la cantidad de ciclos ocupados de forma exacta por el codigo. Casi siempre es mas simple usar otro micro mejor que estar tan jugado con los tiempos. Por ejemplo los cambios de tareas en freeRTOS estan realizados en ASM, para los nucleos ARM.
El que uses ASM reduce la portabilidad/legibilidad del codigo. Si tu codigo esta bien realizado te vas a encontrar que vas a tener todo lo referente a las comunicaciones/configuracion/etc por un lado que es lo que pertenece al micro en cuestion, y por otro lado vas a tener todas las funciones escritas en C, de tal forma que no tenga dependencia del micro que sea que le pongas, incluso podes ponerlo en la PC y vas a ejecutarlo. Ademas no hay necesidades de usar ASM, lo que podes hacer en ASM lo podes hacer en C de forma mas rapida, excepto las funciones DSP de dsPIC, pero Microchip ofrece una funcion C para cada una de esas instrucciones en ASM, el unico caso "obligatorio" que conozco.
te doy un ejemplo, la instrucción nop se demora 1 ciclo de ejecución que en tiempo seria 1us (para un cristal de 4MHz). Si yo tuviera que hacer un delay de ese tiempo, ¿seria mejor usar la función __delay_us(1) o asm("nop")?
Usa __delay_us(1), el compilador se encarga de realizar la rutina para la cantidad de ciclos correctos, es decir un nop. Si observas no podes hacer algo asi:
Algo que podes realizar en CCS, pero en XC8 no, ya que espera una constante, para hacer la subrutina que cumpla con los ciclos necesarios. Si usaste ASM vas a saber que crear una rutina que de de forma exacta la cantidad de ciclos en una demora y que sea variable es bastante complicado. Ya que hacerlo con un for, solo aumentaria el error. Por lo tanto en XC8 si queres crear una demora, la cual NO va a ser exacta, podes hacerlo:
void delay_s(uint16_t t)
{
while(t--) __delay_ms(1000);
}
Tu me has dicho que una de ellas es el while, pero ¿Como podría saber cuanto se demoran esas funciones,para poder optimizar el código?
El mayor problema de poner algo que demore la interrupcion, es que cuando entro a la interrupcion, si no sale por estar ejecutando alguna funcion y aparece otra interrupcion, esta no va a ser atendida hasta que termine la primera, vamos a un ejemplo mas simple y que ocurria con tu programa.
Entraba a la interrupcion apenas recibia 1 caracter. Supongamos que para entrar por la UART un caracter a esos baudios tarda 10ms ( para hacer las cosas redondas ), vos pusiste un delay de 1000ms, eso quiere decir que se perderian casi 100 caracteres, caracteres que siguen llegando. Ademas la UART posee una proteccion, cuando llega un caracter y no se recoge a tiempo (ya que llego otro) se activa un bit de Overrun y se detiene todo el proceso de recepcion, hasta que deshabilites y vuelvas a habilitar el recepcion. Entonces la idea basica es: Recibir el caracter y salir rapido, para que cuando llegue el otro estemos fuera de la interrupcion. Y esto se cumple para TODO, imaginate un Timer con interrupcion cada 100ms y vos con un delay de 1000ms dentro, eso quiere decir que el codigo del Timer va a ejecutarse primer a los 100ms de activado, y luego cuando termine el delay de 1000 va a volver a entrar. Esto aparte te deja "famélico" en tiempo de procesador a tu loop principal, ya que siempre va a estar reentrando a la interrupcion y nunca se va a ejecutar el while, si no crees probalo:
void interrupt ISR(void)
{
PIR9bits.TMRIF = 0; // Borro flag
LATBbits.LATB0 ^= 1;
__delay_ms(1000);
}
void main(void)
{
ConfigTMR(); //Interrupcion cada 100ms
ConfigPin();
ActivoInterrupciones();
while(1)
{
__delay_ms(500); // Observaras que va a quedar aca siempre., nunca va a encenderse/apagarse RB1
LATBbits.LATB1 ^= 1;
}
}
Un while puede ser rapido, pero pensemos por un momento, tanto la recepcion como la transmision dura lo mismo en enviarse un caracter ( suponiendo que los relojes sean iguales ) Si vos dentro de la interrupcion queres enviar 2 o 3 caracteres por cada 1 caracter recibido, te vas a dar cuenta que hay 1 o mas caracteres que se perderian, ademas de tener que lidiar con el problema de Overrun ( el cual no deberia ocurrir nunca ), pero por ejemplo yo puedo hacer sin problemas:
i=0
while(i++<10)
{
LATBbits.LATB0 ^= 1;
}
Use un while, pero el loop es muy simple y se va a ejecutar rapidamente. El problema no es el while, el problema es que no se quede por siempre alli, el problema es Arjona
y mi ultima pregunta es acerca es acerca de la funciones estandar printf() y scanf(). ¿esas funciones sirven para utilizar la UART? (por ejemplo,si uso printf("hola mundo") la función sola configura todo (registros) y envia el string xD)
scanf() no existiria en el mundo de los microcontroladores. A no ser que poseas un OS con un buffer de entrada, pero para cuando lleguemos a eso estariamos hablando seguramente de un microprocesador con algun Linux. O a no ser que lo implementes y crees tu propio scanf
printf() por su parte creo que XC8 provee su soporte, lo que si tenes que definir una funcion putch(), ejemplo:
void putch(char c)
{
while(!TXSTAbits.TRMT);
TXREG = c;
}
Para que entiendas, printf lo unico que hace es "decodificar" el string. si tenias:
el printf va a usar el putch , y va a pasar letra por letra, primero la 'i',' ','=',' ','6' , es decir los caracteres ya formateados.
¿ Es rapido ?, Usualmente es bastante lento el printf, ya que pasar los numeros a caracteres por ahi no es tan simple, enteros no es tanto problema, pero flotantes si es lento.
Otro problema que ves es que en el envio (que usa el printf y que fabricamos) esperamos que se envie caracter por caracter, es decir que va a salir del printf recien cuando termine de enviar todo.
Una cosa mas, como nosotros creamos el putch, podemos mostrarlo en cualquier lado. Puedo usar el SPI, o enviarlo a un LCD, etc
-----------------------------------------------------
Hasta ahora vimos que la recepcion se puede hacer simple y sin esperar, pero la transmision estamos siempre trabados ahi.
Una forma es crear buffers ( si son circulares mas simple su control) de escritura y lectura
volatile char bufferRX[64];
volatile char bufferTX[64];
Ahora cuando entremos a la interrupcion del RX vamos a obviamente escribir sobre ese buffer, luego cuando podamos leemos, pero leemos del buffer!!
Igual configuramos para interrupcion del TX, y esta ves en ves de esperar que se vacie, simplemente ponemos lo que queremos enviar en nuestro buffer, y de alli disparamos la interrupcion. Va a entrar a la interrupcion cada ves que envie un caracter, y dentro de la interrupcion parara, cuando este todo enviado, si mientras esta en medio de un mensaje, agregas al buffer mas datos, no va a existir problemas.