Beruflich Dokumente
Kultur Dokumente
Alejandro
www.infopic.comlu.com
10/04/2010
Tabla de contenido
PIC24 Y C30 ............................................................................................ 3
Programación. ...................................................................................... 5
Introducción: ........................................................................................ 6
Memoria de datos. ............................................................................... 9
Creando el primer Proyecto...................................................................... 11
El Código Fuente ................................................................................... 13
La función principal. ........................................................................... 14
Operación de cambio de clock. ................................................................. 17
Ejemplo en C30: ................................................................................ 18
Algunos aspectos de C30: ........................................................................ 19
Atributos de variables. ......................................................................... 20
Atributos a funciones: .......................................................................... 20
Interrupciones...................................................................................... 21
Registros de control y estado de las interrupciones: ...................................... 22
Manejo de las interrupciones: ................................................................ 23
Módulos temporizadores. ......................................................................... 25
Modos de operación. ........................................................................... 26
Configuracion timer 32-bits. .................................................................. 28
Operación de lectura y escritura. ............................................................ 28
Ejemplo modo temporización, interrupción cada 500ms a 40 MIPS. .................... 29
Selección de pines de los periféricos. .......................................................... 32
UART. ............................................................................................... 34
Baud rate......................................................................................... 35
Configuración de la USART. ................................................................... 35
Uso del printf en C30. .......................................................................... 42
Notificación de cambio de estado de pines. ................................................... 43
Control LCD. Modo 4-bits o 3-pines más registro de desplazamiento ...................... 46
Modificación de funciones de bajo nivel de la librería stdio.h para re-direccionar
salida de datos de printf. ...................................................................... 47
ANEXO A. ........................................................................................... 49
2
PIC24 Y C30
Aquí se presenta una introducción para comenzar a trabajar con los microcontroladores
PIC24 y MPLAB C30.
3
Características:
Para trabajar con el segundo he realizado una placa adaptadora para usar la Multiboard del
amigo Felixls:
PIC24FJ128GA010:
Sencillamente espectacular!!!
Algo que me llama la atención es el DMA, leyendo el datasheet ya nos damos una idea de
que se trata:
Acceso directo a memoria (DMA) es un mecanismo muy eficiente de copia de datos entre
periféricos SFRs (por ejemplo, registro de recepción UART, buffer de entrada Captura 1), y
buffer o variables almacenadas en la memoria RAM, con una intervención mínima del CPU.
El controlador DMA puede copiar automáticamente bloques enteros de datos sin necesidad
de software para leer o escribir en lo SFRs cada vez que se produce una interrupción del
periférico. El controlador DMA usa un bus dedicado para la transferencia de datos y por lo
tanto, no utiliza ciclos de ejecución del código del CPU.
Programación.
Para programarlos hay diversas herramientas, entre las cuales encontramos las de Microchip
(PICKit2, PicKit3, ICD2, ICD3, ect) , GTP-USB [plus], etc. En mi caso he construido el
5
PICKit2 Clone, que junto a Felixls hemos diseñado uno compatible con estos
microcontroladores que trabajan a 3.3V. Por aquí tienen una de las versiones disponibles por
el momento.
Introducción:
Principales características:
• Memoria de programa flash de 4 a 256 Kbytes.
• Memoria RAM de 0.5 a 16Kbytes.
• DMA, 8 canales con 2Kbytes de RAM.
• A/D de 10/12-bits.
6
• Unidad de medida de tiempo de carga (CTMU), una fuente de corriente constante
acoplada al ADC que provee la habilidad de medir capacidades o tiempos con una
resolución de ns. Útil para realizar teclados sensibles al tacto.
7
La CPU de los PIC24 posee una arquitectura Harvard modificada con avanzado set de
instrucciones con palabras de 24-bits de ancho. La memoria de programa tiene 4M-24bits
como espacio de direcciones, pero solo se implementa un porcentaje que dependerá del
dispositivo.
Las instrucciones se ejecutan en un solo ciclo de programa salvo algunas excepciones como
las instrucciones que cambian el flujo del programa, instrucciones de tabla e instrucciones de
acceso a PSV que pueden ocupar más.
Estos microcontroladores poseen 16 registros W de 16 bits, en donde una palabra puede ser
un dato o una dirección. El último registro W (W15) opera como puntero de software de la
memoria de programa para llamadas a subrutinas y pedidos de interrupción
Una propiedad que agrega es la capacidad de que los últimos 32KB de la memoria de datos
pueden ser mapeados en la memoria de programa cómo 16Kword, quedando definida el área
por el registro Program Space Visibility Page (PSVPAG). De esta manera los datos
mapeados en la memoria de programa son accedidos como si fueran de la memoria de datos
y no necesitan de instrucciones especiales para su acceso (por ejemplo, TBLRD, TBLWT).
Espacio de memoria DMA (Solo para los PIC24H), para transferencia de datos desde
periféricos. El controlador DMA usa un bus dedicado para la transferencia de datos y por lo
tanto, no utiliza ciclos de ejecución del código del CPU.
Posee un bloque de multiplicación 17x17 que puede realizar operaciones signadas, sin signo
o mixtas de 16-bits x 16bits o 8-bits x 8-bits en un solo ciclo de programa. Además también
hay un bloque de división de 32-bits o 16-bits dividido 16-bits signado o no signado, que
requiere 19 ciclos de programa.
Memoria de programa.
Memoria de datos.
Como estos dispositivos son de arquitectura Hardvard tienen buses independientes para la
memoria de datos y para la memoria de programa, lo que permite el acceso simultaneo. La
memoria de datos de los PIC24 tiene 16 bits de ancho y su direccionamiento es lineal. El
espacio de datos es accedido por dos unidades generadoras de direcciones (Address
Generation Units o AUGs) para operaciones de lectura y escritura.
Un bloque de direccionamiento efectivo (Effective Address o EA) es responsable del
direccionamiento de la memoria de datos. En la mitad baja del espacio de memoria
9
(EA<15>=0) se implementa la memoria RAM (0000h a 7FFFh), mientras que la mitad alta
es reservado para el área de visualización de la memoria de programa (PSV).
Espacio SFR
Los primeros 2 Kbytes del espacio de memoria es ocupado por los registros de funciones
especiales, estos son usados para el control de operación del CPU y de los periféricos del
mismo.
Memoria RAM DMA.
Los PIC24H disponen de un espacio de memoria con doble puerto que puede ser accedido
simultáneamente por el CPU o por el módulo DMA. Su capacidad depende del dispositivo.
Este espacio de memoria es utilizado por el controlador DMA para transferir datos desde
periféricos que lo soporten, sin utilizar ciclos adicionales del CPU.
Además este espacio de memoria puede usarse para propósitos generales si no se utiliza la
función DMA en el proyecto.
10
Creando el primer Proyecto.
Seleccionamos dispositivo:
11
Creamos y guardamos archivo fuente del proyecto con extensión c, incluyéndolo al mismo:
12
Este archivo informa al enlazador acerca de posiciones predefinidas en la memoria (de
acuerdo a la hoja de datos del dispositivo), así como proporciona información esencial
espacio de memoria, tales como: cantidad total de memoria Flash disponibles, la cantidad
total de memoria RAM disponible, y sus rangos de direcciones. Se encuentra en
\Microchip\MPLAB C30\support\PIC24H\gld.
Con esto, creo que ya estamos listos para programar algo en C30
El Código Fuente
En primer lugar incluimos el archivo *.h del dispositivo utilizado, este contiene definidos los
registros funciones especiales y bits de los mismos.
#include <p24hj128gp502.h>
Ahora sigue la configuración de fuses, en este caso depende del dispositivo utilizado y para
saber que funciones y como configurarlas, debemos ir a la sección de características
especiales en el datashhet.
Para este dispositivo tenemos:
13
Para saber la sintaxis hay que darle una miradita al *.h del dispositivo, al final están los
define de los mismos.
Por ejemplo:
_FOSCSEL(FNOSC_PRIPLL); // Oscilador primario + PLL
_FOSC(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMD_XT); // Conmutación de Clock y
Fail-Safe Clock des-habilitados, OSC2 Clout, Cristal XT.
_FWDT(FWDTEN_OFF);// Watchdog des-habilitado.
_FPOR(FPWRT_PWR2);// Power-on Reset 2ms.
La función principal.
main(void){
Función que debe estar si o si, pues es la primera que se ejecuta al iniciar es sistema, en
donde configuraremos los periféricos y llevaremos control de nuestra aplicación.-
El oscilador primario y el oscilador interno (FRC), puede utilizar el PLL interno para
obtener mayores velocidades de operación, además proporciona una significativa
flexibilidad en la selección de la velocidad de funcionamiento del dispositivo.
La salida del oscilador primario o FRC, el cual denominamos 'Fin', se divide por un factor
de pre-escale (N1), de 2, 3, ... o 33 antes de ingresar al PLL de tal manera que esté entre el
intervalo de 0,8 MHz a 8 MHz. El factor de pre-escaler ‘N1’ se selecciona mediante los bits
PLLPRE <4:0> del registro CLKDIV.
14
El divisor de realimentación del PLL, es seleccionado mediante los bits PLLDIV <8:0> del
registro PLLFBD, que proporciona un factor ‘M‘ (2…513), por el que se multiplica la
entrada al PLL. Este factor debe ser elegido de modo que la frecuencia resultante de salida
esté en la gama de 100 MHz a 200 MHz.
La salida del PLL se divide por un factor post-escaler ‘N2’. Este factor se selecciona
mediante el los bits PLLPOST <7:6> del registro CLKDIV. ‘N2’ puede ser 2, 4 u 8, y
deberán seleccionarse de forma que la frecuencia de salida PLL (FOSC) esté en el rango de
12,5 MHz a 80 MHz, lo que genera una velocidad de funcionamiento del dispositivo de
6.25-40 MIPS.
Fosc=Fin*(M/(N1*N2))
Tenemos que Fcy=Fosc/2, pero además con los bits DOZE<14:12> del registro CLKDIV
podemos aplicar un pos-escaler entre 2,4,8,16,32,64 o 128.
PLLFBD = 0x0000;
CLKDIV = 0x004E;
Bueno, hasta aquí ya hemos definido algunos detalles de como va a trabajar nuestro
microcontrolador, ahora hagamos un ejemplo sencillo de hacer titilar los led ubicados en
RB12-RB15.
La librería para agregar demoras a nuestro programa libpic30.h, pero tiene un bug, no se
puede usar a 40MIPs Para utilizar demoras se debe incluir la librería, definir la
frecuencia FCY y disponemos de la función __delay32(Numero de ciclos) con la cual
podemos hacer demoras mayores a 12 ciclos. Luego tenemos definidos 2 macros:
#if !defined(FCY)
extern void __delay_ms(unsigned long);
extern void __delay_us(unsigned long);
#else
#define __delay_ms(d) \
{ __delay32( (unsigned long) (d)*(FCY)/1000); }
#define __delay_us(d) \
{ __delay32( (unsigned long) (d)*(FCY)/1000000); }
#endif
El problema a 40MIPs surge en la definición del macro, pues d*FCY se va de rango de los
32 bits cuando d es mayor a 107 aprox. y genera una demora incorrecta, para solucionarlo
hay que cambiar a unsigned long long para trabajar con 64-bits. También podemos agregar
una demora de segundos.
15
Ejemplo:
while(_LOCK==0);
#include <p24fj128ga010.h>
_CONFIG1( JTAGEN_OFF & GCP_OFF & GWRP_OFF & BKBUG_OFF & COE_OFF & ICS_PGx2
& FWDTEN_OFF & WINDIS_OFF & FWPSA_PR128 & WDTPS_PS32768);
_CONFIG2( FNOSC_PRI & FCKSM_CSDCMD & POSCMOD_HS );
TRISF=0xFFF3;
LATFbits.LATF2=0;
LATFbits.LATF3=0;
while(1){
LATFbits.LATF2=0;
__delay_ms(500);
LATFbits.LATF2=1;
__delay_ms(500);
}
16
Operación de cambio de clock.
En esta arquitectura se puede cambiar entre 4 fuentes de reloj (Primario, LP, FRC y LPRC)
por medio de software cuando se desee. El primario tiene 3 sub modos de operación (XT,
HS y EC) que son configurados mediante los bits de configuración, ósea que en tiempo de
operación no se puede cambiar de modo, pero si se puede cambiar desde o hacia la fuente
primaria.
Para habilitar el cambio de clock, el bit de configuración FCKSM1 del registro de
configuración debe ser programado como 0.
Los bits COSC (OSCCON<14:12>) reflejan la selección de la fuente de clock de los bits
de configuración FNOSC, y los bits de control NOSC (OSCCON<10:8>) controlan la
selección de clock cuando el cambio está habilitado. El bit de control OSWEN
(OSCCON<0>) inicia el cambio y muestra la finalización, pero si el cambio no esta
habilitado, este bit siempre se sostiene a 0.
Secuencia de cambio de oscilador.
1. Si se desea, para determinar la fuente de clock actual se debe leer los bits COSC
(OSCCON<14:12>).
2. Realizar la secuencia de desbloqueo para permitir la escritura del byte alto del registro
OSCCON.
3. Escribir el valor apropiado a los bits de control NOSC (OSCCON<10:8>) para la nueva
fuente de clock.
4. Realizar la secuencia de desbloqueo para permitir la escritura del byte bajo del registro
OSCCON.
5. Setear el bit OSWEN (OSCON<0>) para iniciar el cambio de oscilador.
Una vez que esta secuencia ha sido completada, el hardware responderá de la siguiente
manera:
1. Se compara los bits COSC con los bits NOSC, si son iguales el bit OSWEN es borrado
automáticamente y el cambio de clock es abortado.
2. Si el cambio de clock es valido, los bits LOCK y CF del registro OSCCON son
borrados.
3. El nuevo oscilador es encendido por el hardware y se espera el tiempo adecuado. Si
usará PLL, se espera hasta que el PLL se enganche. (LOCK=1)
4. El hardware espera 10 ciclos de clock de la nueva fuente y recién realiza el cambio.
5. Se borra el bit OSWEN para indicar la finalización de la transición, y los bits COSC se
actualizan con los de NOSC.
6. La fuente antigua de clock es apagada.
17
Ejemplo en C30:
Pasando de oscilador principal XT de 4MHz sin PLL (2 MIPS) a oscilador principal XT con
PLL (40 MIPS).
while(time!=0){
asm volatile("repeat #34");
asm volatile("nop");
time--;
}
}
main(void){
18
__builtin_write_OSCCONL(0x01); // Iniciamos cambio.-
while(OSCCONbits.OSWEN == 1) {}; // Esperamos a que se termine el
cambio de clock.
while(1){
LATB=0xF000;
Delay_ms(1000);
LATB=0x0000;
Delay_ms(1000);
}
}
Tipos de variables:
Variables enteras:
Variables flotantes:
19
Atributos de variables.
En C30 para fijar atributos especiales a las variables se utiliza la palabra __attribute__ la
cual es seguida por los atributos dentro de paréntesis dobles. Los atributos pueden ser:
• address
• aligned
• deprecated
• far
• mode
• near
• noload
• packed
• persistent
• reverse
• section
• sfr
• space
• transparent_union
• unordered
• unused
• weak
Ejemplos:
Atributos a funciones:
También se utiliza la palabra clave __attribute__ para dar una propiedad a una función, los
soportados son:
• address (addr)
• alias ("target")
• const
• deprecated
• far
20
• format (archetype, string-index, first-to-check)
• format_arg (string-index)
• interrupt [ ( [ save(list) ] [, irq(irqid) ] [, altirq(altirqid)] [,preprologue(asm) ] ) ]
• near
• no_instrument_function
• noload
• noreturn
• section ("section-name")
• shadow
• unused
• weak
Ejemplos:
Interrupciones.
La arquitectura de los PIC24 proporciona 118 fuentes de interrupción las cuales poseen un
vector propio para cada una de ellas, a diferencia de la familia 16F que tiene solo un vector,
y la familia 18F la cual posee 2 vectores para baja y alta prioridad. En este caso no solo
existen 2 niveles de prioridad, sino 8 que pueden asignarse a cada evento. Además de las
interrupciones tenemos 8 TRAPs, que son eventos prioritarios (Sin niveles de prioridad) de
falla del sistema.
Los vectores están agrupados en dos tablas, la principal (IVT-Interrupt Vector Table) y
alternativa (AIVT-Alternate Interrupt Vector Table) la cual es para soporte de debbug.
21
bit ALTIVT (INTCON2 <15>), el cual si está en alto todas las interrupciones y
excepciones son procesadas en éste.
IFSx
Estos registros tienen todos los flags de las peticiones de interrupción. Cada fuente de
interrupción tiene su bit de estado, que es seteado por el periférico respectivo y es borrado
mediante software.
IECx
22
Estos registros tienen los bit habilitadores/des-habilitadores de cada fuente de interrupción.
IPCx
Estos registros asignan a cada fuente de interrupción un nivel de prioridad entre 8
disponibles.
INTTREG
Este registro contiene el numero del vector de interrupción asociado y el nuevo nivel de
prioridad de interrupción del CPU (VECNUM<6:0> y IRL<3:0>). El nuevo nivel de
prioridad de interrupción es la prioridad de la interrupción pendiente.
STATUS/CONTROL REGISTERS
Aunque no son específicamente parte del hardware de control de interrupción, los registros
de control del CPU contienen bit que controlan la funcionalidad de las interrupciones.
El registro SR contiene los bits IPL<2:0> que indican el actual nivel de prioridad de
del CPU. El CPU tiene 16 niveles de prioridad (0-15), y una fuente de interrupción o
una Traps debe tener mayor prioridad para que pueda generar una excepción en el
proceso.
El registro CORCOn contiene el bit IPL3, que junto a IPL<2:0> indican el nivel
actual de prioridad del CPU. Este bit es solo de lectura para que los eventos traps
(Niveles de prioridad de 8 a 15) no puedan ser enmascarados por el software del
usuario.
Inicialización.
1. Setear el bit NSTDIS si no se desea interrupciones anidadas.
2. Seleccionar el nivel de prioridad para las fuentes de interrupción escribiendo el bit de
control en el regsitro IPCx apropiado. El nivel de prioridad depende de la aplicación
específicamente y el tipo de fuente de interrupción. Si no se desea niveles de prioridad
múltiples, los bits de control de los registros IPCx para todas las fuentes de interrupción
habilitadas pueden ser programadas al mismo valor distinto de cero.
3. Borrar la bandera de interrupción asociada en el registro IFSx.
4. Habilitar la fuente de interrupción seteando el bit de control asociado en el registro
apropiado IECx.
Las rutinas de interrupción (ISR) son como cualquier otra función que utilizan variables
locales o globales pero que no tienen parámetros de entrada, no retornan valores y no pueden
ser llamadas como otras funciones. Las ISR solo sirven para invocarse a través de una
interrupción por hardware o traps. Para dar por terminada la ISR se utiliza la instrucción (en
assembler, oculta para el usuario de C30) RETFIE la cual devuelve el valor salvado a PC, y
restablece el nivel anterior de prioridad del CPU.
23
Una recomendación es no llamar a otras funciones dentro de la ISR por problemas de
latencia.
Para declarar una función como una ISR, se le debe asignar el atributo interrupt, siendo la
sintaxis la siguiente:
}
void __attribute__((__interrupt__)) _AltStackError(void){
El parámetro opcional save se utiliza para guardar y restablecer una o más variables al entrar
y salir de la ISR.
void __attribute__((__interrupt__(__save__(var1,var2))))isr0(void);
Des-habilitación de interrupciones.
Cada fuente de interrupción tiene su bit de control para habilitarla o des-habilitarla, pero si
desea deshabilitar todas las interrupciones se puede establecer el nivel de prioridad del CPU
mayor al de las interrupciones (máximo nivel 7).
También es posible usar la instrucción DISI que deshabilita las interrupciones (Niveles de
prioridad 1 a 6) durante un determinado número de instrucciones (Máximo 0x3FFF). Esta
instrucción trabaja en conjunto con el registro DISICNT. Si el registro es distinto de cero,
las interrupciones con nivel de prioridad entre 1 a 6 son deshabilitadas, y este registro se
decrementa por cada ciclo. Cuando este registro llega a 0 las interrupciones son re-
habilitadas.
24
__asm__ volatile ("disi #30"); // Deshabilita interrupción durante 30
ciclos.
__asm__ volatile ("disi #0x1E"); // Deshabilita interrupción durante 30
ciclos.
Módulos temporizadores.
Estos microcontroladores disponen de timers de 16-bits que pueden dividirse en tres tipo de
acuerdo a sus funcionalidades:
Timer tipo A (Timer1)
Timer tipo B (Timer2, 4, 6 y 8 )
Timer tipo C (Timer3, 5, 7 y 9)
Los timers de tipo A, a diferencia de los demás, pueden operar desde un oscilador de baja
potencia de 32kHz y poseen el modo de contador asincrónico desde fuente de clock externa.
En cambio los timers de tipo B y C pueden combinarse para formar timer de 32-bits.
Además los timers de tipo C pueden activar la conversión AD.
25
Esquema Timer tipo C:
Modos de operación.
En los modos temporizador y Gated se utiliza como clock el ciclo de instrucción interna
26
(Fcy), en cambio, en los modos contador sincronico y asincrónico se utiliza el clock externo
derivado del pin TxCK. Para control del modo de operación se utilizan los siguientes bits:
• TCS (TxCON<1>): Bit que determina fuente de clock.
• TSYC (TxCOM<2>): Bit para determinar si el clock externo se sincroniza con el ciclo de
instrucciones (Únicamente timers tipo A).
• TGATE (TxCON<6>): Bit que determina modo Gate.
El clock de entrada (FCY o pin TxCK) de todos los timers poseen la opción de ser
preescalado entre 1:1, 1:8, 1:64 o 1:256, esta configuración de preescaler se realiza mediante
los bits TCKPS<1:0> del registro TxCON. La cuenta de los preescaler es borrada cuando se
efectua una escritura sobre los registros TMRx y TxCON, cuando se deshabilita del timers
(TON=0) y al ocurrir un reset.
Los modos temporización, contador asincrónico y sincrónico son bien conocidos en las
familias 16F y 18F, por lo que solo voy a enfocarme en el modo Gate:
Cuando se selecciona el modo Gate, el timer puede ser usado para la medir la duración de
una señal externa. En este modo el timer se incrementa al recibir un flanco ascendente en el
pin TxCK y continua incrementándose a razón del clock interno (FCY) mientras este se
mantenga en nivel alto (1). Cuando se recibe un flanco descendente en el pin TxCK se
detiene la cuenta y se genera una interrupción.
Para seleccionar este modo hay que setear el bit TGATE y borrar el bit TCS. Además se
debe configurar el pin TxCK como entrada.
main(void){
27
void __attribute__((__interrupt__, __shadow__)) _T1Interrupt(void){
static unsigned int Cuenta;
Cuenta=TMR1;
IFS0bits.T1IF = 0; // Borramos flag.
}
Ejemplo:
28
main(void){
Cuenta=TMR2;
Cuenta+= TMR3HLD*0x10000;
IFS0bits.T3IF = 0; //Borramos flag.
}
main(void){
// Cristal de 4MHz y PLL trabajando a 40 MIPS.
PLLFBD = 0x004E; // M=80.
CLKDIV = 0x0000; //N1=2, N2=2.
29
T2CONbits.TGATE = 0; // Mode Gate deshabilitado.
T2CONbits.TCKPS = 0b00; // Preescaler 1:1
/* ** Debemos contar 20.000.000 ciclos y generar la interrupción
** */
PR3 = 0x0131; // Fijamos periodo (msw)
PR2 = 0x2D00; // Fijamos periodo (lsw)
while(1){}
}
También podemos utilizar las funciones de las librerías que incluyen C30 para el manejo de
periféricos. Documentación sobre ella encontramos en …\Microchip\MPLAB
C30\docs\periph_lib . Para el control de de timers tenemos:
Funciones:
• CloseTimerx
• CloseTimerxy
• ConfigIntTimerx
• ConfigIntTimerxy
• OpenTimerx
• OpenTimerxy
• ReadTimerx
• ReadTimerxy
• WriteTimerx
• WriteTimerxy
Los parámetros disponibles para cada función están bien detallados en la ayuda.
30
_T3IF = 0; //Borramos flag.
}
/* ***************************************************************** */
main((int argc, char * argv[]){
// Cristal de 4MHz y PLL trabajando a 40 MIPS.
PLLFBD = 0x004E; // M=80.
CLKDIV = 0x0000; //N1=2, N2=2.
while(_LOCK==0);
WriteTimer23(0x00);
while(1){}
}
#include <p24fj128ga010.h>
_CONFIG1( JTAGEN_OFF & GCP_OFF & GWRP_OFF & BKBUG_OFF & COE_OFF & ICS_PGx2
& FWDTEN_OFF & WINDIS_OFF & FWPSA_PR128 & WDTPS_PS32768);
_CONFIG2( FNOSC_PRI & FCKSM_CSDCMD & POSCMOD_HS );
#include <timer.h>
/* ***************************************************************** */
void __attribute__((__interrupt__, __shadow__)) _T3Interrupt(void){
TRISD=0xF0FF;
WriteTimer23(0x00);
while(1){
}
Que desperdicio hacer titilar un led con tremenda maquina pero es el precio de ir
adquiriendo conocimientos de forma ordenada y no estamparnos contra la pared y no saber
para donde correr.
31
Selección de pines de los periféricos.
Esta característica que implementa esta familia de microcontroladores esta enfocado a hacer
mucho más flexible la utilización de ellos, más aun en microcontroladores de pocos pines.
La selección de pines de los periféricos permite a los usuarios determinar que pines usar en
una amplia gama de pines I/O. Al aumentar las opciones de patillas disponibles en un
dispositivo en particular, podemos adaptar mejor el microcontrolador a la aplicación, en
lugar de adecuar la aplicación al dispositivo.
Los pines disponibles para selección dependen del microcontrolador particularmente. Estos
pines incluyen la designación RPn (Remappable peripheral y n indica el número)
Los periféricos que pueden utilizar la selección de pines son únicamente digitales, estos
incluyen las comunicaciones seriales (UART y SPI), entrada a clock externo de timers, pin
para captura o comparación y pines de interrupción externa. Hay periféricos digitales que no
pueden utilizar la multiplexión de pines por necesitar un hardware especial, como es el caso
de la comunicación I2C.
32
En cambio la selección de pines con la función de entrada es inversa, con la utilización del
registro RPORx se asigna un periférico a un pin determinado. Además tiene la opción
NULL (00000) que permite dejar desconectado el pin. (Sin selección)
33
// Habilitamos escritura sobre registros RPINRx y RPORX.
__builtin_write_OSCCONL(OSCCON & ~(0x40));
RPINR18bits.U1RXR = 9; //Asignamos pin recepción UART1 a RP9.-
RPOR4bits. RP8R = 0b00011; // Asignamos RP8 al pin de transmisión UART1.
// Bloqueamos escritura sobre registros RPINRx y RPORX.
__builtin_write_OSCCONL(OSCCON |0x40);
UART.
Registros de control.
Baud rate.
Para determinar el baud rate tenemos 2 opciones, baud rate de alta velocidad BRGH=1 o de
baja velocidad. Las ecuaciones asociadas son: (UxBRG de 16-bits)
Configuración de la USART.
La USART usa el formato estándar NRZ, con un bit de Start, 8 o 9 bits de datos y 1 o 2 bits
de Stop. Además tenemos la posibilidad de configurar paridad como par, impar o ninguna.
Esto lo configuramos mediante los bits PDSEL<1:0> y STSEL. (UxMODE)
El módulo USART es habilitado mediante el seteo del bit UARTEN (UxMODE) y el bit
UTXEN (UxSTA). Una vez habilitado, los pines UxTX y UxRX son configurados como
35
salida y entrada respectivamente, sustituyendo los valores seteados al TRIS correspondiente.
Para deshabilitar el módulo se debe borrar el bit UARTEN (UxMODE), al deshabilitarlo los
pines se comportan con I/O adoptando la configuración del TRIS. Además se borra el buffer,
el baud rate se restablece y también todos los bits de estados y errores son reseteados.
Transmisión.
El modo de funcionamiento es más o menos similar a las otras familias, difiere en que hay 3
modos de selección de la interrupción:
• UTXISEL<1:0>=00, el bit UxTXIF es seteado cuando un carácter es transferido desde el
Buffer al registro UxTSR, esto implica que una posición esta libre en el buffer.
• UTXISEL<1:0>=01, el bit UxTXIF es seteado cuando el ultimo carácter es transferido
desde el registro UxTSR, esto implica que todas las operaciones de transmisión han sido
completadas.
• UTXISEL<1:0>=10, el bit UxTXIF es seteado cuando el carácter es transferido al
registro UxTSR y el buffer de transmisión está vacio.
Recepción.
Estos modos pueden ser cambiando durante la operación. Tenemos los bits URXDA y
UxRXIF que indican el estado del registro UxRXREG, y el bit RIDLE (UxSTA<4>) que
muestra el estado del registro UxRSR. Cuando el registro UxRSR esta vacio, el bit RIDLE
está en 1.
36
3. Si se utilizará interrupción determinar el modo mediante los bits URXISEL, habilitar la
interrupción mendiante el bit UxRXIE, y establecer prioridad (UxRXIP).
4. Habilitar modulo UART seteando bit UARTEN.
5. Si la interrupción no está habilitada, se debe recibir por poleo mediante el bit URXDA.
Si se usa interrupción, el bit UxRXIF debe ser borrado por software.
6. Leer dato desde buffer, si se ha seleccionado trabajar con 9-bits se recibe un Word, sino
un byte.
Hay varias otras operaciones que se pueden realizar que dependerán de la aplicación a
desarrollar, como transmisión a 9-bits, detectar automáticamente dirección, modo Loopback,
auto-Baud Rate, operación con control de flujo mediante hardware, soporte infrarrojo,
soporte LIN, ect. cuya información esta bien detallada en el datasheet del dispositivo.
Ejemplo:
char DataUART;
char KbhitUART;
/* **
**************************************************************************
********* ** */
// Definciones para calculo de Baud Rate.-
#define FCY 40000000 // MHz
#define BAUDRATE 9600
#define BRGHigh 1
#if BRGHigh
#define BRGVAL ((FCY/BAUDRATE)/4)-1
#else
#define BRGVAL ((FCY/BAUDRATE)/16)-1
37
#endif
/* **
**************************************************************************
********* ** */
void __attribute__((__interrupt__, __shadow__)) _U1RXInterrupt(void){
DataUART=getcUART1();
KbhitUART=1;
_U1RXIF=0; // Borramos flag.
}
/* **
**************************************************************************
********* ** */
main(void){
const char Texto[]= "Probando comunicación serial...\r\nPIC24HJ128GP502 xD
...\r\n";
38
IFS0bits.U1RXIF=0; // Borramos flag.
IEC0bits.U1RXIE=1; // habilitamos interrupcion por recepción.
KbhitUART=0;
putsUART1(Texto);
while(1){
if(KbhitUART==1){
KbhitUART=0;
putcUART1(DataUART);
}
}
}
Para el control de UART C30 dispone de la librería uart.h la cual tiene las siguientes
funciones:
• BusyUARTx
• CloseUARTx
• ConfigIntUARTx
• DataRdyUARTx
• getsUARTx
• OpenUARTx [dsPIC30F]
• OpenUARTx [dsPIC33F/PIC24H]
• putsUARTx
• ReadUARTx o getcUARTx
• WriteUARTx o putcUARTx
char DataUART;
char KbhitUART;
/* **
**************************************************************************
********* ** */
39
// Definciones para calculo de Baud Rate.-
#define FCY 40000000 // MHz
#define BAUDRATE 9600
#define BRGHigh 1
#if BRGHigh
#define BRGVAL ((FCY/BAUDRATE)/4)-1
#else
#define BRGVAL ((FCY/BAUDRATE)/16)-1
#endif
/* **
**************************************************************************
********* ** */
void __attribute__((__interrupt__, __shadow__)) _U1RXInterrupt(void){
DataUART=getcUART1();
KbhitUART=1;
_U1RXIF=0; // Borramos flag.
}
/* **
**************************************************************************
********* ** */
main(void){
const char Texto[]= "Probando comunicación serial...\r\nPIC24HJ128GP502 xD
...\r\n";
SetPriorityIntU1RX(1);
EnableIntU1RX;
40
KbhitUART=0;
putsUART1(Texto);
while(1){
if(KbhitUART==1){
KbhitUART=0;
putcUART1(DataUART);
}
}
}
#include <p24fj128ga010.h>
_CONFIG1( JTAGEN_OFF & GCP_OFF & GWRP_OFF & BKBUG_OFF & COE_OFF & ICS_PGx2
& FWDTEN_OFF & WINDIS_OFF & FWPSA_PR128 & WDTPS_PS32768);
_CONFIG2( FNOSC_PRI & FCKSM_CSDCMD & POSCMOD_HS );
#include <uart.h>
char DataUART;
char KbhitUART;
/* ** ************************************************************** ** */
// Definciones para calculo de Baud Rate.-
#define FCY 10000000 // MHz
#define BAUDRATE 9600
#define BRGHigh 1
#if BRGHigh
#define BRGVAL ((FCY/BAUDRATE)/4)-1
#else
#define BRGVAL ((FCY/BAUDRATE)/16)-1
#endif
DataUART=getcUART2();
KbhitUART=1;
_U2RXIF=0; // Borramos flag.
}
/* ** ************************************************************** ** */
int main (int argc, char * argv[]){
41
BRGVAL);
KbhitUART=0;
putsUART2(Texto);
while(1){
if(KbhitUART==1){
KbhitUART=0;
putcUART2(DataUART);
}
}
}
Para utilizar printf en nuestros proyectos se debe agregar la librería stdio.h, pero además se
42
debe inicializar el Heap Size, yendo a Project/Buil Options/Project, y en la solapa
MPLAB LINK30 llenar el campo, yo he colocado 256.
Configuración:
1. Colocar los pines como entrada digital setenado los bits correspondientes en el TRISx.
2. Habilitar la interrupción para los pines seleccionados seteando los bits correspondientes
en CNEN1 y CNEN2.
3. Si se desea habilitar las resistencias pull-up internas setear los bits correspondientes en
CNPU1 y CNPU2.
4. Fijar prioridad de interrupción usando los bits CNIP<2:0>.
5. Habilitar interrupción seteando bit CNIE.
Cuando la interrupción ocurre, se debe leer el registro PORT asociado al pin necesario para
detectar la condición de desigualdad y preparar la lógica para detectar un próximo cambio.
Interrupción externa
Nota: Revisando las funciones vemos que para control de cambio solo se tiene en cuenta
desde 0 hasta 23, pero este PIC tiene 24, 27, 29 y 30 Así que nos utilizables, hay que
43
utilizar configuración mediante bits.
#else
#define BRGVAL ((FCY/BAUDRATE)/16)-1
#endif
#define Col0 PORTBbits.RB12
#define Col1 PORTBbits.RB13
#define Col2 PORTBbits.RB14
#define Col3 PORTBbits.RB15
__delay_ms(30);
Fil0=1;Fil1=1;Fil2=1;Fil3=1;
for(i=0;i<4;i++){
switch(i){
case 0: Fil0=0;break;
case 1: Fil1=0;Fil0=1;break;
case 2: Fil2=0;Fil1=1;break;
case 3: Fil3=0;Fil2=1;break;
}
if(!Col0) break;
44
j++;
if(!Col1) break;
j++;
if(!Col2) break;
j++;
if(!Col3) break;
j++;
}
Fil0=0;Fil1=0;Fil2=0;Fil3=0;
while((!Col0)|(!Col1)|(!Col2)|(!Col3));
__delay_ms(30);
if(j<16){
Tecla=Teclas[j];
KbhitTecla=1;
}
LATB=PORTB;
_CNIF=0; // Borramos flag.
}
/* ** ************************************************************** ** */
main(void){
// Configuramos PLL para trabajar a 40 MIPS con Cristal de 4MHz.-
PLLFBD = 0x004E; // M=80.
CLKDIV = 0x0000; //N1=2, N2=2.
// Cambiamos clock para incorporar PLL.-
__builtin_write_OSCCONH(0b011); // Indicamos cambio a clock
primario con PLL.-
__builtin_write_OSCCONL(0x01); // Iniciamos cambio.-
while(OSCCONbits.OSWEN == 1) {}; // Esperamos a que se termine el
cambio de clock.
45
_CNIP=1; // Prioridad en 1.
_CNIE=1; // Habilitamos interrupción.
KbhitTecla=0;
while(1){
if(KbhitTecla){
KbhitTecla=0;
printf("Tecla:-> %c\r\n",Tecla);
}
}
}
En este caso solo posteo una librería para manejo LCD compatible con Hitachi 44780 y que
modificaciones hacer para usar printf para escribir en el LCD. La librería dispone de la
posibilidad de trabajar en modo de 4-bits con R/W siempre y cuando se seleccionen pines
tolerables a 5V para dispositivos de 3.3V o sin R/W. También implementa el manejo del
LCD mediante 3 pines y un registro de desplazamiento (4094, 74164, ect) todos estos modos
se selecciona con 2 definiciones (USE_RW y USE_3PINES).
Tiene implementada las siguientes funciones:
• Open_LCD(Tipo)
o Tipo = LINEA_5X7,LINEA_5X10 o LINEAS_5X7
• BusyLCD();
• WriteCmdLCD(cmd)
• putcLCD(data) .Con reconocimiento de caracteres especiales ‘\f’,’\n’
• putsLCD(*data)
• GotoxyLCD(x,y)
Ejemplo:
Modo 4 bits con R/W:
46
Modificación de funciones de bajo nivel de la librería stdio.h para re-direccionar
salida de datos de printf.
Podemos utilizar la función printf para formatear datos y enviar por puerto UART, SPI,
CAN, I2C (Caso especial) y LCD entre otros ejemplos. Una forma fácil que encontré fue la
siguiente:
También debemos agregar la definición de la función externa utilizada, por ejemplo para el
caso del LCD:
Y no debemos olvidarnos de agregar el archivo write.c al proyecto para que sea re-
compilado y efectivizar los cambios. (Se adjunta write.c)
Quedando la librería con la modificación para usar printf en el envío de datos al LCD:
47
Ejemplo para un PIC24F: Ver que se cambia las funciones que realizan la configuración
de fuses. Se configura para oscilador HS sin PLL, por ejemplo 20 MHZ, 10MIPS.
#include <p24fj128ga010.h>
/* ** ***************************************************************** */
_CONFIG1( JTAGEN_OFF & GCP_OFF & GWRP_OFF & BKBUG_OFF & COE_OFF & ICS_PGx2
& FWDTEN_OFF & WINDIS_OFF & FWPSA_PR128 & WDTPS_PS32768);
_CONFIG2( FNOSC_PRI & FCKSM_CSDCMD & POSCMOD_HS );
/* ** ************************************************************** ** */
//#define FCY 10000000UL
#include "LCD_C30.h"
#include <stdio.h>
int __OUT_PER=1;
/* ** ************************************************************** ** */
main(void){
OpenLCD(LINEAS_5X7);
while(1){}
}
int __OUT_PER=1;
/* ** ************************************************************** ** */
main(void){
48
primario con PLL.-
__builtin_write_OSCCONL(0x01); // Iniciamos cambio.-
while(OSCCONbits.OSWEN == 1) {}; // Esperamos a que se termine el
cambio de clock.
OpenLCD(LINEAS_5X7);
while(1){
}
Librerías en anexo A.
ANEXO A.
Librerías para control de LCD:
/*******************************************************************
\file LCD_30.h
\version: 1.0
\author Suky.
\web www.infopic.comlu.com
\mail inf.pic.suky@live.com.ar
\date 18/01/10
*******************************************************************/
#if defined(__dsPIC30F__)
#include <p30fxxxx.h>
#elif defined(__dsPIC33F__)
#include <p33Fxxxx.h>
#elif defined(__PIC24H__)
#include <p24Hxxxx.h>
#elif defined(__PIC24F__)
#include <p24Fxxxx.h>
#endif
#ifndef _LCDPIC24_H
#define _LCDPIC24_H
49
/* ** Para no utilizar el PIN RW comentar la siguiente definición ** */
//#define USE_RW
#ifndef USE_3PINES
/* ** Pines tolerables a 5V para usar RW ** */
#define DATA_PIN_7 LATBbits.LATB11
#define DATA_PIN_6 LATBbits.LATB10
#define DATA_PIN_5 LATBbits.LATB9
#define DATA_PIN_4 LATBbits.LATB8
/* Definiciones generales */
// Encendido display.
#define D_ON 0x0C
#define D_OFF 0x08
#define CURSOR_ON 0x0E
#define BLINK_ON 0x0F
#define C_ON_BLINK 0x0D
// Control.
#define D_CLEAR 0x01
#define SET_INIT 0x02
/*;--- Modos de Entrada
; Incrementa Direccion, Display fijo 0x06
; Decrementa Direccion, Display fijo 0x04
; Incrementa Direccion, Cursor fijo 0x07
; Decrementa Direccion, Cursor fijo 0x05
;--- Corriemiento Cursor
; Cursor a la Izquierda 0x10
50
; Cursor a la Derecha 0x14
; Display a la Izquierda 0x18
; Display a la Derecha 0x1C
;--- Fijar Sistema
; Bus 8 bits, 1 línea, 5x7 0x30
; Bus 8 bits, 1 linea, 5x10 0x34
; Bus 8 bits, 2 líneas, 5x7 0x38
; Bus 4 bits, 1 línea, 5x7 0x20
; Bus 4 bits, 1 linea, 5x10 0x24
; Bus 4 bits, 2 líneas, 5x7 0x28*/
/* Funciones */
void OpenLCD(unsigned char LcdType) __attribute__ ((section
(".libperi")));
#endif
/*******************************************************************
\file LCD_30.c
\version: 1.0
\author Suky.
\web www.infopic.comlu.com
\mail inf.pic.suky@live.com.ar
\date 18/01/10
********************************************************************/
#include "LCD_C30.h"
#ifdef USE_RW
DATA_PIN_7 = 0;
DATA_PIN_6 = 0;
DATA_PIN_5 = 0;
DATA_PIN_4 = 0;
TRIS_DATA_PIN_7 = 1;
TRIS_DATA_PIN_6 = 1;
51
TRIS_DATA_PIN_5 = 1;
TRIS_DATA_PIN_4 = 1;
RW_PIN = 1;
RS_PIN = 0;
E_PIN=1;
__delay_us(2);
if(READ_PIN_7==1){
E_PIN = 0;
__delay_us(2);
E_PIN = 1;
__delay_us(2);
E_PIN = 0;
RW_PIN = 0;
return 1;
}else{
E_PIN = 0;
__delay_us(2);
E_PIN = 1;
__delay_us(2);
E_PIN = 0;
RW_PIN = 0;
return 0;
}
#else
__delay_ms(1);
return 0;
#endif
}
#ifdef USE_3PINES
unsigned char i;
unsigned char cmd_temp;
#endif
while(BusyLCD());
#ifndef USE_3PINES
#ifdef USE_RW
RW_PIN = 0;
#endif
RS_PIN = 0;
TRIS_DATA_PIN_7 = 0;
TRIS_DATA_PIN_6 = 0;
TRIS_DATA_PIN_5 = 0;
TRIS_DATA_PIN_4 = 0;
52
__delay_us(1);
CLOCK_PIN=0;
}
#endif
Nop();
E_PIN = 1;
__delay_us(2);
E_PIN = 0;
#ifndef USE_3PINES
DATA_PIN_7 = (unsigned int)((cmd & 0x08)>>3);
DATA_PIN_6 = (unsigned int)((cmd & 0x04)>>2);
DATA_PIN_5 = (unsigned int)((cmd & 0x02)>>1);
DATA_PIN_4 = (unsigned int)(cmd & 0x01);
#else
cmd_temp=(cmd&0x0F); // Rs es bit 4
for(i=0;i<8;i++){
DATA_PIN=(unsigned int)((cmd_temp & 0x80)>>7);
cmd_temp<<=1;
CLOCK_PIN=1;
__delay_us(1);
CLOCK_PIN=0;
}
#endif
Nop();
E_PIN = 1;
__delay_us(2);
E_PIN = 0;
#ifndef USE_3PINES
/* ** Configuración de pines ** */
DATA_PIN_7 = 0;
DATA_PIN_6 = 0;
DATA_PIN_5 = 0;
DATA_PIN_4 = 0;
#ifdef USE_RW
RW_PIN = 0;
#endif
RS_PIN = 0;
E_PIN = 0;
TRIS_DATA_PIN_7 = 0;
TRIS_DATA_PIN_6 = 0;
TRIS_DATA_PIN_5 = 0;
TRIS_DATA_PIN_4 = 0;
#ifdef USE_RW
TRIS_RW = 0;
#endif
TRIS_RS = 0;
TRIS_E = 0;
#else
DATA_PIN = 0;
CLOCK_PIN = 0;
53
E_PIN = 0;
TRIS_DATA = 0;
TRIS_CLOCK = 0;
TRIS_E = 0;
for(i=0;i<8;i++){
DATA_PIN=0;
CLOCK_PIN=1;
__delay_us(1);
CLOCK_PIN=0;
}
#endif
/* ** INICIALIZACION ** */
#ifndef USE_3PINES
DATA_PIN_5 = 1;
DATA_PIN_4 = 1;
#else
temp=0x03;
for(i=0;i<8;i++){
DATA_PIN=(unsigned int)((temp & 0x80)>>7);
temp<<=1;
CLOCK_PIN=1;
__delay_us(1);
CLOCK_PIN=0;
}
#endif
for(i=0;i<3;i++){
E_PIN = 1;
__delay_ms(2);
E_PIN = 0;
__delay_ms(5);
}
#ifndef USE_3PINES
DATA_PIN_4 = 0;
#else
temp=0x02;
for(i=0;i<8;i++){
DATA_PIN=(unsigned int)((temp & 0x80)>>7);
temp<<=1;
CLOCK_PIN=1;
__delay_us(1);
CLOCK_PIN=0;
}
#endif
E_PIN = 1;
__delay_us(2);
E_PIN = 0;
54
while(BusyLCD());
switch(data){
case '\f':
WriteCmdLCD(0x01);
break;
case '\n':
GotoxyLCD(1,2);
break;
default:
#ifndef USE_3PINES
#ifdef USE_RW
RW_PIN = 0;
#endif
RS_PIN = 1;
TRIS_DATA_PIN_7 = 0;
TRIS_DATA_PIN_6 = 0;
TRIS_DATA_PIN_5 = 0;
TRIS_DATA_PIN_4 = 0;
55
__delay_us(2);
E_PIN = 0;
}
Direccion+=x-1;
while(BusyLCD());
WriteCmdLCD(0x80|Direccion);
}
while(*buffer != '\0') {
WriteDataLCD(*buffer++);
}
}
56
57