MICROCONTROLADOR
DSPIC30F4013 PARA
NOVATOS
Escrito por: Luis Enrique Azáldegui Vegaso
                                  INTRODUCCIÓN
La siguiente guía ha sido empezada a ser redactada el día 10 de mayo del 2019, bajo el
contexto de un estudiante que apenas empieza a explorar esta asignatura; o ya de plano,
el       mundo          de      los        microcontroladores        en        general.
Espero que en versiones futuras, esta pequeña guía pueda tener un acabado más
“profesional”, por así decirlo. Con el pasar de las ediciones (si sigo redactando), el
contenido seguirá ampliándose.
Bienvenido, querido lector, a esta humilde guía.
                     CAPÍTULO 0 - ¿POR DÓNDE EMPIEZO?
Consigue los siguientes materiales para poder empezar a testear 1.
   -   Multitester (multímetro).
   -   dsPIC30f4013
   -   PICKIT V3
   -   Circuito de grabado (lo veremos más adelante)
   -   Leds
   -   Resistencias de 1k
También vas a necesitar los siguientes programas:
   -   MPLABX (la versión más reciente disponible)
   -   PICKIT 3 (la versión más reciente disponible)
Para esta versión no detallaré en el entorno de MPLABX ni en el troubleshooting2
necesario para las versiones más tempranas de esta guía de repaso.
Es más, en este caso al ser una primera versión y ser un público específico el que lo va a
leer, este capítulo queda aquí.
       CAPÍTULO 1 – DATOS TÉCNICOS E INSTRUCCIONES BÁSICAS
El microcontrolador dsPIC30f4013 tiene un CPU con arquitectura Harvard. Se le dice así
porque los buses de la memoria de programa y datos no son compartidos.
La memoria de programa (FLASH) tiene una capacidad de 48KB; en cambio, la memoria
de datos (RAM) sólo tiene una capacidad de 2KB.
Cada instrucción que escribimos en el microcontrolador tiene una extensión de 24 bits; o
en otras palabras, 3 bytes.
Por lo tanto, la capacidad real del procesador es: 48KB  16 KB .
                                                       3
En la memoria RAM, los datos son de 16 bits; o que es lo mismo, 2 bytes. Este valor en
particular es el que vamos a tener muy presente.
Capacidad de palabra = 216  65536
Este valor, 65536 es importantísimo cuando trabajemos con cantidades. ¿Qué pasa si
quieres cagar un valor que exceda #0xFFFF? O en otras palabras, ¿qué pasa cuando
ingresas un valor que excede el valor máximo para una palabra de 16 bits? El valor se
desborda, es decir, que se sale del rango máximo admisible. Más adelante veremos por
qué esto es importante.
El dsPIC30f4013 cuenta con 5 puertos IO3 y su cantidad de pines es la siguiente:
Puerto A: 1 pin (RA11)
Puerto B: 13 pines (<RB0 – RB 12>)
Puerto C: 3 pines (<RC13 – RC15>)
Puerto D: 6 pines (<RD0 – RD3>; RD8 – RD9)
Puerto F: 7 pines (<RF0 – RF6>)
El dsPIC30f4013 cuenta con 16 registros de trabajo, desde W0 hasta W15; y cada uno
tiene una capacidad de 16 bits.
Ahora ya que esos detalles técnicos están dados, vayamos a ver un par de instrucciones
básicas y su funcionamiento.
                                      MOV lit,Wx
Esta función mueve un literal (lit) al registro Wx (registro de trabajo). Un literal es un
número, generalmente expresado en hexadecimal. Por ejemplo, movamos el número 12
al registro de trabajo W1.
                                   MOV #0x000C,W1
El numeral (#) denota que se trata de un literal. Si no estuviera así, se haría referencia a
una posición de memoria. Lo cual nos lleva a lo siguiente.
                                   MOV 0X000C,W1
Esta instrucción toma la posición de memoria 000C y la pasa al registro de trabajo W1.
Por ahora no trabajaremos punteros así que sólo lo tomaremos como referencia.
                                   MOV Wx, PORTx
Ahora la misma instrucción puede mover un valor desde un registro de trabajo a otro
completamente distinto. En este caso, le está pasando el valor de Wx al PORTx.
                                   .equ VAR, lit
Esta instrucción declara una variable en una posición de memoria. Por algún motivo, así
tenga el numeral adelante, funciona como posición. Apégate a esto y ya. Trata de que
cada variable tenga una posición única.
                                    MOV W2, VAR
Mueve el valor del registro W2 a la variable VAR. Aquí es cuando recién se le asigna un
valor a una variable.
Ten muy en cuenta que para pasar valores a cualquier lado, necesitas pasárselo primero
a un registro de trabajo, y luego a tu destino. Por ejemplo, digamos que quiero prender
un led en el pin RB0 del puerto B. Debería mandar un 0001 al PORTB. Y se haría de la
siguiente forma:
                                 MOV #0x0001, W0
                                   MOV W0, PORTB
Se entiende la idea, ¿verdad?
                                       DEC VAR
Esta instrucción decrementa en 1 a la variable VAR. Viene del inglés “decrease”, en
español “decremento”.
                                   BSET Ws,#bit4
Esta instrucción toma el registro Ws (no de trabajo, sino un registro particular, como
podría ser PORTB) y pone en alto el pin especificado por #bit4, el cual es una palabra de
16 bits, y siempre se declara con el numeral. Por ejemplo, digamos que queremos declarar
en estado alto el pin RB0 del Puerto B:
                                   BSET LATB,#0
Tomas el latch del Puerto B (lo especificaré adelante), y va a agarrar el pin #0, es decir
RB0, y lo pondrá en alto. Esta instrucción es una reducción de “bit set”, en español
vendría siendo “bit en alto”.
                                   BCLR Ws,#bit4
Es exactamente lo mismo que BSET, solo que esta vez lo pondrá en nivel bajo. Es una
reducción de “bit clear”, en español vendría siendo “bit en bajo”.
                                   BTG Ws,#bit4
Esta vez tomará el pin #bit4 del registro Ws y alternará su valor al opuesto del que esté.
En inglés es una reducción de “bit toggle”… Toggle, eso suena familiar, ¿verdad? En
español significa “conmutar” para nuestros propósitos. Así que si el #bit4 está en 0,
cambiará a 1, y si está en 1, pasará a 0.
                                   BTSC Ws, #bit4
Es una reducción aproximada del inglés “bit test is clear”; en español, vendría a ser
“probar si el pin está en nivel bajo”, ya llevándolo a lenguaje de electrónica. Como la
traducción que he hecho indica, va a primar el #bit4 del registro Ws. Si esto es cierto,
entonces saltará la siguiente instrucción; es decir, que la instrucción inmediatamente
siguiente no será leída, y leerá la siguiente a esa.
                                   BTSS Ws, #bit4
Reducción aproximada de “bit test is set”; en español para electrónica sería algo como
“probar si el pin está en nivel alto”. Exactamente lo mismo que el anterior, solo que la
instrucción saltará si el #bit4 está en nivel alto.
                                    BRA etiqueta
Aquí voy a hacer dos pausas. Uno, esta instrucción es un salto condicional sin condición.
Y lo otro, una etiqueta, como veremos más adelante en un ejemplo, son como los
marcadores de un libro, donde uno puede volver luego.
Esta instrucción va a hacer un salto hacia la etiqueta llamada “etiqueta” sin ninguna
condición, es decir, que apenas se invoque esta instrucción, saltará.
                                 BRA NZ, etiqueta
Esta vez, el salto condicional sí tiene una condición, “No Zero”, del inglés. Su significado
es evidente. Entonces, cuando se invoque esta instrucción, si la operación anterior (dígase,
un decremento de una variable) no evoca un cero, entonces saltará a la etiqueta “etiqueta”.
                                  BRA Z, etiqueta
Exactamente lo mismo al anterior, solo que el salto lo dará si se evoca un cero.
                                   CALL etiqueta
Esta instrucción llama a una etiqueta, no salta hacia ella. ¿Qué diferencia sustancial hay?
Primero veamos la siguiente instrucción:
                                         return
Return, del inglés que significa “retroceder”, vuelve desde donde ha sido llamada una
operación; o en este caso, una etiqueta. De ahí, cuando tengamos una subrutina y debamos
volver al programa principal, se vuelve con return; mientras que la subrutina ha sido
accedida mediante un call.
                                    goto proceso
Esta instrucción va hacia una etiqueta llamada proceso. No es igual que call, puesto que
esa instrucción llama una etiqueta para que vaya a ese lugar, cosa que es posible usar el
comando return con facilidad; en cambio, goto va a ese lugar, y usar el return podría
significar un resultado inesperado.
Por ahora, esas son todas las instrucciones que necesitamos. Vayamos al siguiente punto.
        CAPÍTULO II – CONFIGURACIÓN Y MANEJO DE PUERTOS
Los puertos del dsPIC30f4013 cuentan con tres registros que permiten su manejo:
TRISx: Maneja los pines IO y los declara. Si un pin es declarado como 0, se comporta
como salida; en cambio, si es declarado como 1, se comporta como entrada.
PORTx: Maneja el estado de lectura del puerto.
LATx: Maneja el estado de escritura del puerto.
Ahora, se puede escribir tanto en PORTx como en LATx; pero el problema empírico de
escribir en PORTx, es que no te garantiza un cambio de estado. Recuerda, si quieres
escribir en el registro, utiliza LATx; si quieres leer el estado de un pin (aun así esté
comportándose como salida), utiliza PORTx.
Veamos un ejemplo donde se declaran todos los pines del puerto B como entradas, y
cómo es que se declara el puerto B como entrada/salida digital.
MOV #0x0000, W0
MOV W0, TRISB        ; Declara todos los pines del Puerto B
                     ; como salidas digitales.
MOV #0xFFFF, W0
MOV W0, ADPCFG ; Declara el puerto B como entradas y
                     ; salidas digitales.
El registro ADPCFG se encarga de ver el estado analógico/digital del Puerto B, puesto
que por defecto, todas sus entradas y salidas están configuradas como analógicas.
Después de todo, trabajamos con un PIC especializado en el procesamiento de señales,
¿no? Al declarar cada estado del registro con 1’s, todos los pines del puerto B se
comportarían de manera digital.
Intentemos prender un led en el pin RB0 del Puerto B.
                                   BSET LATB,#0
Digamos que queremos apagarlo luego de un tiempo… Un tiempo, ¿cómo se supone que
haga que se retrase? Puedes hacer que una variable empiece a bajar hasta cero, cosa que
ese pequeño tiempo que se demora te permita ver el parpadeo constante del led. Es algo
demasiado rústico, pero nos permite también ver cómo funcionan las instrucciones paso
a paso.
.equ VAR1, #0X0802                         ; inicializar la variable en
                                           ; la posición 0802
MOV #0xFFFF, W0
MOV W0, VAR1                 ; darle un valor real a la variable VAR1
call delay
BCLR LATB,#0
delay:              DEC VAR1      ; decrementar en 1 la variable VAR1
                    BRA NZ, delay
                    return
Bueno, hasta aquí hemos configurado el Puerto B, hemos declarado una variable, hemos
encendido y apagado un led ubicado en un pin del Puerto B, y hemos hecho un
pequeñísmo retraso. Pero hay algo que debes tener en cuenta al programar en ASM:
necesitas dos variables obligatorias y reservadas, y una instrucción de inicio.
.global __reset                   ; Marca donde se reinicia el
                                  ; programa cuando el dsPIC se
                                  ; reinicia en su pin número 1.
.text         ; Marca el inicio de donde arranca el programa
main ;   Si bien esta etiqueta podría no ser obligatoria
     ;   realmente, siempre es bueno, por temas de orden al
     ;   programar, tener esta etiqueta marcando el inicio de
     ;   tu programa entero
Entonces veamos ahora un ejemplo de un programa entero como debería estar en
MPLABX.
;=======================================================
;CONFIGURACIÓN INICIAL
;=======================================================
.include "p30f4013.inc" ; Inclusión de la librería del
                        ; dsPIC30F4013
;-------------------------------
;BITS DE CONFIGURACIÓN – Esto no lo explicaré por ahora,
                         ; sólo copia si necesitas
;-------------------------------
config     __FOSC,CSW_FSCM_OFF&XT_PLL4    ;Oscilador de
                                     ; cristal con PLL 4
config     __FWDT,WDT_OFF                  ;Desabilitando Watchdog Timer
config     __FBORPOR,PBOR_ON&MCLR_EN
                                 ;Set Brown-out Reset voltage and
                                 ;and set Power-up Timer to 16msecs
config     __FGS,CODE_PROT_OFF                  ; Desabilitado protección
                                                ; de código
;------------------------------------------
;DEFINICIÓN DE VARIABLES Y SUBRUTINAS
;------------------------------------------
.equ VAR1, #0x0802
.global __reset ; variable obligatoria de reinicio
.text
;=======================================================
;INICIO DEL PROGRAMA PRINCIPAL
;=======================================================
__reset:
    goto main
    .org 100
    main:
    nop     ; sin operación. Salta un ciclo de instrucción
;-----------------------------------------------------
;PROGRAMA PRINCIPAL: TEST DE LED
;-----------------------------------------------------
MOV #0x0000, W0
MOV W0, TRISB   ; Declara todos los pines del Puerto B
                ; como salidas digitales.
MOV #0xFFFF, W0
MOV W0, ADPCFG ; Declara el puerto B como entradas y
                ; salidas digitales.
Bucle: MOV #0xFFFF, W0
MOV W0, VAR1         ; darle un valor real a la variable VAR1
BSET LATB,#0
call delay
BCLR LATB,#0
call delay
goto bucle
delay:          DEC VAR1   ; decrementar en 1 la variable VAR1
                      BRA NZ, delay
                      return
.end
Fíjate bien que luego de .end, la instrucción que indica el final del programa, siempre se
deja como mínimo una línea más en blanco. De esta manera, sí compila el programa.
                                  CAPÍTULO III – TIMERS
El dsPIC30F4013 cuenta con 5 timers distintos de 16 bits; pero en esta ocasión, solo
utilizaremos el primero.
Dentro del datasheet de la familia dsPIC30f, podemos encontrar la siguiente tabla, la cual
tiene resaltada 4 bloques.
TON: Timer ON. enciente o apaga el timer
1: Activa el timer.
0: Detiene el timer.
TCKPS<1:0>: Timer Input Clock Prescale Select Bits. En conjunto controlan el prescaler.
1:1 = 1:256 valor de prescaler
1:0 = 1:64 valor de prescaler
0:1 = 1:8 valor de prescaler
0:0 = 1:1 valor de prescaler
TCS: Timer Clock Source Select. Elige la fuente del temporizador.
1: Fuente externa en el pin TxCK
0: Reloj interno (Fosc/4)
Los bloques en gris son irrelevantes, así que para métodos prácticos, los tomaremos como
ceros. Y por ahora no nos adentraremos en los demás bloques.
Por ahora trabajaremos el timer 1 en modo temporizador.
Los timers cuentan con 3 registros leíbles y escribibles:
TMRx: registro contador, de 16 bits, del timer.
PRx: registro periódico, de 16 bits, asociado con el timer.
TxCON: registro de control, de 16 bits, asociado con el timer.
Interpretemos esto de una manera más simple:
 TON       TSIDL                              TGATE   TCKPS1   TCKPS0       TSYNC   TCS
       0              0   0   0   0   0   0                             0                 0
Esta tabla la usaremos más adelante para configurar el registro TxCON.
Veamos ahora el diagrama de bloques del timer tipo A, el mismo tipo del timer 1.
El registro TMRx sumará una unidad cada vez que pase cierto tiempo. Dicho tiempo se
calcula multiplicando el Tiempo de ciclo de instrucción con el prescaler.
                                           1
                                  Tcy              ...(1)
                                          FCY
Si por ejemplo estamos trabajando con un oscilador de 4MHz y un valor PLLx4, entonces
el Tiempo de ciclo de instrucción será de 250ns.
                                       Fosc  PLL
                               FCY                    ...(2)
                                            4
El registro PRx es un valor fijo que se comparará con TMRx; cuando estos dos sean
iguales, TMRx se reiniciará, y al mismo tiempo el pin TxIF pasará a estar a nivel alto.
Pero ojo, TxIF tiene que limpiarse manualmente, no vuelve al estado bajo por su cuenta.
Un esquema simplificado puede verse aquí, cortesía del Profesor Astocondor.
¿Entonces qué valor debería tomar el registro PRx? Veamos la siguiente fórmula:
El tiempo de interrupción es el tiempo deseado, en el que la operación del dsPIC será
interrumpida. Digamos que por ejemplo queremos encender y apagar un led durante un
segundo, entonces nuestro tiempo de interrupción será 1 segundo. El tiempo de ciclo de
instrucción ya lo vimos, al igual que el prescaler. Ahora, hacemos que TMRx sea igual a
0; eso significa la iteración inicial, cuando el timer va a empezar a contar, el tiempo cero.
Y una pausa aquí, antes de pasar a mayores detalles, ¿recuerdas que en el capítulo I
mencioné el desborde del valor máximo admisible para una variable de 16 bits? PRx
NO debe exceder el valor 65536; caso contrario, el propio MPLABX no compilará tu
programa.
Nuevamente, cortesía del Profesor Astocondor, veremos los tiempos máximos admisibles
para cada prescaler:
           T_interrupción _máximo = 250ns x Prescaler x (65536-0) || TMR1=0 y PR1=65536.
                                Prescaler 1: T_interrupción = 16,38ms
                                   Prescaler 8: T_interrupción=0.131s
                                   Prescaler 64:T_interrupción=1.048s
                               Prescaler 256: T_interrupción=4.194s
Tenemos dos opciones para nuestro ejemplo de un retardo de 1 segundo: o bien tomamos
el prescaler de 64, o bien el de 256. En este caso, tomaremos el de 256.
                                   T int  Tcy  Presc  PRx
                                               1
                                   PRx 
                                         250 109  256
                                   PRx  15625
Hagamos uso de la tabla ahora que sabemos qué prescaler vamos a utilizar.
 TON       TSIDL                             TGATE    TCKPS1    TCKPS0       TSYNC   TCS
 1     0   0       0   0   0   0    0   0    0        1         1        0   0       0     0
He sombreado cada nibble para poder convertirlo a hexadecimal de manera más sencilla.
TON es declarado en 1 para poder hacer uso del timer 1. TSIDL, TGATE y TSYNC son
dejados en 0 por ahora. TCKPS1 y TCKSP0 son configurados como 1:1, lo que indica un
prescaler de 256. Y TSYNC tomará el valor de 0 para utilizar el oscilador de cristal.
Convirtiendo este arreglo en hexadecimal, nos dará el siguiente valor: # 0 x8030 .
Ahora que tenemos este valor, ya podemos empezar a configurar en nuestra programación.
Primera acotación, cada X que se vea en un registro, deberá ser reemplazada por el número
del timer correspondiente. En este caso, X pasará a ser 1.
CLR T1CON ; limpiamos el estado del timer 1 en su totalidad
CLR TMR1      ; limpiamos el estado actual del registro TMR1
MOV #15625,W0         ; le pasamos el dato calculado para un retardo
                      ; de 1 segundo
MOV W0,PR1            ; pasamos del registro de trabajo al registro
                      ; PR1
MOV #0x8030,W0 ; le pasamos el dato de la tabla para activar
               ; el timer y un prescaler de 256
MOV W0,T1CON          ; le pasamos el valor desde el registro de
                      ; trabajo al registro T1CON
Ya hemos configurado nuestro timer en modo temporizador para un retardo de 1 segundo.
Ahora veamos cómo hacer una rutina simple para encender y apagar un led
;---- Configuración estándar del puerto B ---------
MOV #0x0000,W0
MOV W0,TRISB
MOV #0xFFFF,W0
MOV W0,ADPCFG
;----------------------------------------------------------
Loop:
            BTSS        IFS0,#T1IF          ;   Si el pin T1IF, la salida del
                                            ;   registro IFS0 que mantiene
                                            ;   control de si PR1 y TMR1 son
                                            ;   iguales, está en nivel alto,
                                            ;   saltará la siguiente
                                            ;   instrucción
            BRA         Loop         ; repite el ciclo si TMR1 y PR1 no
                                     ; son iguales
            BCLR        IFS0,#T1IF                  ; Borro el estado del
                                                    ; flag
            BTG       LATB,#0                       ; Conmuto el valor de RB0
BRA   Loop   ; Inicio nuevamente el
             ; ciclo
                        CAPÍTULO IV – INTERRUPCIONES
Una interrupción es, en palabras resumidas, una rutina que puede ser llamada con
prioridad. Es decir, que esta, en lugar de ser llamada por una instrucción como call o goto,
es bien llamada por la terminación de otra rutina mediante un flag, o es llamada por un
periférico (dígase un botón o un sensor, por ejemplo), y provoca la desviación del código
principal.
Cada interrupción dentro del dsPIC está alojada en una porción de memoria específica;
iniciando desde la posición 0x0004.
Cuando un periférico envía una señal de interrupción, se activa una bandera o flag. En
otras palabras, es un funcionamiento análogo a un final de carrera.
Dentro del dsPIC existen distintos registros que permiten habilitar, controlar y establecer
niveles de prioridad de las interrupciones.
Para fines prácticos, nos concentraremos en los registros INTCON2, IFS0 e IEC0.
También tendremos en consideración la instrucción DISI, exclusiva de los dsPIC, que nos
permite desactivar las interrupciones por un determinado número de instrucciones.
No hay demasiado que destacar en esta sección así que simplemente veremos un código
a forma de ejemplo:
;=======================================================
;CONFIGURACIÓN INICIAL
;=======================================================
.include "p30f4013.inc" ; Inclusión de la librería del
dsPIC30F4013
;-------------------------------
;BITS DE CONFIGURACIÓN
;-------------------------------
config __FOSC,CSW_FSCM_OFF&XT_PLL4                       ;Oscilador de
cristal con PLL 4
config     __FWDT,WDT_OFF                    ;Watchdog Timer: OFF
config     __FBORPOR,PBOR_ON&MCLR_EN ;MCRL: ON
config __FGS,CODE_PROT_OFF                        ; Desabilitado
protección de código
;------------------------------------------
;DEFINICIÓN DE VARIABLES Y SUBRUTINAS
;------------------------------------------
.equ VAR1, #0x0802
.global __reset ; variable obligatoria de reinicio
.global __INT0Interrupt ; variable que define la
interrupción 0 en el pin INT0
.org 0x0000 ; define el origen de la siguiente instrucción
__reset:
    goto main
    .org 0x000014
    goto __INT0Interrupt
    .text
    main:
    nop     ; sin operación. Salta un ciclo de instrucción
;==========================================
;CONFIGURACIÓN DE LOS PUERTOS
;==========================================
bclr TRISB,#0    ;Pin RB0 como salida
bclr TRISB,#1    ;Pin RB1 como salida
bset TRISA,#11 ;RA11/INT0 como entrada
mov #0xFFFF,W0
mov W0,ADPCFG    ;Puerto B como IO Digital
mov #0x0000,W0
mov W0,PORTB     ;Limpia el Puerto B
; Configuración Interrupción 0 - INT0
bclr INTCON2,#INT0EP ;Interrupción 0 en flanco positivo
bclr IFS0,#INT0IF      ;Limpia el flag de interrupción en INT0
bset IEC0,#INT0IE      ;Habilita la interrupción externa en
INT0
; Configuración Timer 1
clr T1CON      ;Limpia el estado completo del Timer 1
clr TMR1       ;Limpia el estado actual del regristro TMR1
mov #15625,W0
mov W0,PR1     ;TMR1 en 1 segundo
mov #0x8030,W0
mov W0,T1CON     ;valor 256 en prescaler, TMR1 = ON
       goto loop_principal
;=======================================================
;INICIO DEL PROGRAMA PRINCIPAL
;=======================================================
loop_principal: btss IFS0,#T1IF        ;espera que se llene el
registro
                      ; del TMRR1
            bra loop_principal
            bclr IFS0,#T1IF       ;limpia el flag en el TMR1
            btg LATB,#0 ;conmuta el pin RB0
            bra loop_principal
__INT0Interrupt: DISI #3         ;deshabilita las interrupciones
por
                     ;3 instrucciones
          bclr IFS0,#INT0IF       ;limpia el flag de
interrupción
            btg LATB,#1
            bclr IFS0,#T1IF       ;limpia el flag en el TMR1
            RETFIE    ;devuelve el control al programa
principal
.end
                CAPÍTULO V – PERIFÉRICOS: PANTALLA LCD
Uno de los temas fundamentales de la realización de un proyecto, es siempre el manejo
de periféricos. Ya sea un teclado, un sensor, o en este caso, una pantalla LCD.
Hasta el momento hemos visto la programación del microcontrolador en lenguaje
Assembly. La idea de ello ha sido acostumbrarnos a programar parte por parte. Si tú
dominas ASM, puedes adaptarte fácilmente a C, que es justamente el lenguaje que
usaremos a partir de ahora. Como paréntesis, la programación en Assembly aún es
utilizada para tareas relativamente simples pero que requieran mayor precisión y menor
tiempo de respuesta.
Crearemos un nuevo proyecto en MPLABX, y esta vez hacemos uso de dos carpetas
distintas: Header y Source. En la primera irán los archivos de cabecera (con extensión .h),
y en Source irá nuestro archivo principal y otro más según convengamos (con
extensión .c).
Vamos a Header Files, click derecho y creamos un nuevo archivo xc16_header.h. En caso
de que no se pueda crear ese tipo de archivo, le damos a Other, buscamos la carpeta C, y
seleccionamos C header file.
Le damos como nombre config, y aceptamos. Luego de crear el archivo, borra todo su
contenido.
Arriba en la pestaña Window, buscamos Target Memory Views, y luego Configuration
Bits.
Se nos abrirá una pantalla inferior como esta:
Por ahora sólo nos cercioraremos de tres configuraciones:
   -   El oscilador (FOSC) debe ser o bien XT o XT con PLL4 para un cristal de
       4MHz, que es con el que se está trabajando hasta ahora. Cualquier otra
       especificación depende de tu cristal de cuarzo o tu fuente de reloj (en caso uses
       un Oscilador RC, lo cual no es del todo aconsejable).
   -   El Watchdog Timer (FWDT) usualmente se deja apagado.
   -   La protección de código (FGS) casi siempre vas a necesitar dejarla apagada.
       Si la dejases encendida, una vez quemes el programa en el dsPIC, no podrás leer
       lo que tenga grabado luego.
Una vez se tengan todas las configuraciones necesarias, le damos click a Generate Source
Code to Output. Luego de unos segundos, se generará un código disponible para copiar y
pegar. Lo tomamos y pegamos el contenido en config.h. De esta manera, ya configuramos
el dsPIC.
Ahora crearemos otro archivo de cabecera, al cual llamaremos clock. Este archivo
contendrá la información correspondiente a la frecuencia de ciclo de instrucción.
Escribiremos la siguiente línea de código:
#define FCY 4000000
En este caso le estoy dando un valor de 4 106 que corresponde a 4MHz. Pero esto sólo
ocurre porque en mi configuración le di un oscilador XT_PLL4. Para comprender esto,
veamos la siguiente fórmula que nos brinda el datasheet de la familia dsPIC30f:
                                           FOSC  PLL
                                   FCY 
                                              4
La frecuencia de ciclo de instrucción FCY es determinada por la frecuencia del oscilador,
el PLL, y dividida entre 4. Como nuestro PLL, en este caso, es 4, ambos factores se
cancelan, dándonos un valor de 4000000. Ese valor obtenido por fórmula es el que le
daremos al código. Por ejemplo, si solo estuviese configurado como XT, la instrucción
se vería de la siguiente forma:
#define FCY 1000000
Por último, le daremos clic a la carpeta Header Files, y luego a Add existing ítem. Ahí,
buscaremos el archivo xlcd.h y le damos aceptar.
Nos encontraremos con un código de 121 líneas aproximadamente. Esta es la
configuración de la pantalla LCD. Por ahora, sólo nos importarán las líneas 8 a la 19.
No lo había mencionado hasta ahora, pero el comando #define literalmente se traduce
como “definir”. Exceptuando palabras reservadas por el sistema (como FCY en la parte
anterior), esto nos permite crear “comandos”, por así decirlo.
Veamos las líneas 8 y 9.
Amos hacen referencia al puerto de datos, y está declarado como Puerto D. Y esto está
asociado directamente a los pines de datos de la pantalla LCD.
          Ilustración 1- LCD pinout (Fuente: https://components101.com/16x2-lcd-pinout-datasheet)
Como podemos observar, los pines 7 ~ 14 son pines de datos, de los cuales solo haremos
uso de 4: los pines 11 al 14. Por defecto (en este archivo), la pantalla LCD está
configurada para el modo de 4 bits. Como mencioné antes, los pines de datos están
configurados como Puerto D. Específicamente, los pines RD0, RD1, RD2 y RD3.
Personalmente, no aconsejo cambiar esto.
De las líneas 14 a la 19, están configurados los pines de control.
#define RW_PIN               LATBbits.LATB9                  /* PORT for RW */
#define TRIS_RW              TRISBbits.TRISB9                      /* TRIS for RW */
#define RS_PIN               LATBbits.LATB11                   /* PORT for RS */
#define TRIS_RS              TRISBbits.TRISB11                       /* TRIS for RS */
#define E_PIN                LATBbits.LATB10                   /* PORT for E                */
#define TRIS_E               TRISBbits.TRISB10                       /* TRIS for E                  */
Si vemos el datasheet de la pantalla LCD, los pines 5, 6 y 7 tienen nombres abreviados
como RS, RW y E respectivamente. Y como podemos observar en el código, estos están
declarados. Por ejemplo, el pin RW dentro de la configuración está configurado en el pin
RB9, tanto en el registro LATB como TRISB. Si bien estos pines no desaconsejo
cambiarlos, puedes hacerlo si necesitas disponer de esos pines para fines de conversión
analógica-digital (cosa que veremos más adelante).
Las líneas 8 a la 19 son las únicas que pueden ser editadas, las demás deben permanecer
como están.
Todo lo que está en este archivo son definiciones, así como funciones prototipo que serán
declaradas dentro del archivo xlcd.c, que es justamente el siguiente archivo que
incluiremos en un momento. Antes de pasar a eso, cabe resaltar lo siguiente: cada
definición dentro del archivo está comentada. Es decir, que además de que las
definiciones tienen un nombre sugerente, la tarea a la que hacen referencia se puede leer
explícitamente en su comentario a la derecha. Tomemos por ejemplo la línea 32:
#define CLEAR_XLCD                   0x01            /* Clears the LCD             */
Como vemos en su comentario, del inglés al español, dice “Limpia la LCD”. Esta
definición, en conjunto con una función que veremos más adelante, borra (o mejor dicho,
limpia) el contenido que se esté mostrando en la pantalla en ese momento. De forma
similar, cada función tiene su descripción para saber qué hace cada una.
Una vez terminamos con los archivos de cabecera, iremos a los archivos fuente. Le damos
clic derecho a Source Files, Add existing ítem, y buscamos el archivo xlcd.c.
Y la única edición que debemos hacer, es fijarnos que al inicio estén incluidas las
siguientes librerías:
#include <xc.h>
#include "clock.h"
#include <libpic30.h>
#include "xlcd.h"
El archivo clock.h siempre debe estar antes de libpic30 y xlcd.h porque es necesario que
libpic30 y xlcd detecten que FCY está definido. Se aconseja el orden mostrado. Y por
cierto, la diferencia entre los signos mayor-menor y las comillas dobles, es que el primero
hace referencia a archivos dentro de la carpeta XC16 en la carpeta Microchip en tu
computadora; mientras que el segundo hace referencia a archivos locales en la carpeta de
tu proyecto.
Ahora lo único que falta es nuestro archivo principal. Vamos a Source Files, clic derecho,
New, y mainXC16.c. En caso de que no te aparezca, dentro de Other puedes buscarlo
dentro de Microchip Embedded en XC16 Compiler.
Empezamos incluyendo las librerías igual que en el apartado anterior.
#include "config.h"
#include "clock.h"
#include <libpic30.h>
#include "xlcd.h"
 Una vez incluidas las librerías y los archivos de cabecera, ya podemos empezar a
programar. En este caso, haremos algo sencillo: escribir un mensaje en pantalla, que dure
un tiempo, lo borre, y empiece a mostrar un mensaje parpadeante.
int main(){
       XLCDInit();
       XLCDgotoXY(1,0);
       putrsXLCD("HOLA MUNDO");
       __delay_ms(3000);
       while(1){
              XLCDgotoXY(0,0);
              putrsXLCD("TRABAJANDO...");
              __delay_ms(500);
              WriteCmdXLCD(CLEAR_XLCD);
              __delay_ms(500);
       }
       return 0;
}
La función XLCDInit() inicializa la pantalla LCD. Esto es obligatorio al inicio.
La función XLCDgotoXY(x,y) mueve el cursor a la posición x e y. Ten en cuenta que la
pantalla que utilices será la que debas tener como referencia. En este caso, por ejemplo,
usamos una pantalla de 16x2. X e Y empiezan siempre a contar desde 0. Como pueden
ver en la programación, pone x  1 e y  0 . Es desde esta posición indicada por
XLCDgotoXY donde se empezará a escribir texto en pantalla (segunda fila, primera
columna).
La función putrsXLCD() escribe en la pantalla desde la posición que esté especificada.
Ten encuenta la cantidad de caracteres que admite tu pantalla por fila.
La función __delay_ms() es un retraso especificado en milisegundos.
La función WriteCmdXLCD() recibe cualquier comando definido en el archivo xlcd.h.
En este caso, le estamos pasando la definición CLEAR_XLCD para poder borrar el
contenido de la pantalla.
         CAPÍTULO VI: MÓDULO UART Y COMUNICACIÓN SERIAL
UART viene de las siglas en inglés Universal Asnchronous Receiver-Transmiter, que en
español se traduce como Transmisor-Receptor Asíncrono Universal.
El módulo del UART es un canal de comunicación asíncrono full-duplex4 que se
comunica con periféricos y computadoras personales, usando protocolos como RS-232,
RS-485. LIN 1.2 e IrDA.
El módulo también incluye el codificador y decodificador IrDA.
Por ahora, en esta versión de este “libro” para estudiantes, no voy a ahondar en mucho
detalle más allá del mencionado. Haré mención de sólo los registros que utilizaremos para
comunicar el dsPIC30f4013 al ordenador.
 UxMODE: Registro en modo UARTx
Bit 15 UARTEN: habilita el módulo UARTx
1 = Habilita el módulo UARTx.
0 = Deshabilida el módulo UARTx.
Bit 3 BRGH: bit de selección de velocidad de baudios
1 = alta velocidad (factor 4)
0 = baja velocidad (factor 16)
 UxBRG: registro de tasa de baudios UARTx
Bit 15 BRG <15:0> bits del divisor de velocidad en baudios
Con respecto a este último registro, el UART tiene un generador de velocidad de baudios5
completo de 16 bits para permitir la máxima flexibilidad de velocidad de transmisión en
baudios.
El BRG funciona en modos de alta o baja velocidad, según lo seleccionado por la
velocidad de transferencia de baudios (BRGH) en el registro UxMODE en su pin 3.
El modo de alta velocidad BRGH  1 genera 4 pulsos de reloj por cada bit de tiempo. Por
el otro lado, el modo de baja velocidad BRGH  0 genera 16 pulsos de reloj por cada bit
de tiempo.
Según el datasheet del dsPIC30f4013, podemos encontrar las siguientes fórmulas para el
modo de baja velocidad BRGH  0 :
La empresa Microchip nos muestra el siguiente ejemplo para una frecuencia de ciclo de
instrucción de 4MHz, una tasa de baudios de 9600 y un modo de baja velocidad:
Donde se puede apreciar que UxBRG  25 , que es el valor que le daremos dentro de la
programación.
A propósito, para el modo de alta velocidad BRGH  1 , las fórmulas cambian a la
siguiente forma:
Bien, pero hay algo que recalcar en este punto: el módulo UART es incompatible con
las entradas USB por defecto. Es necesario convertir la norma RS-232 a TTL, para lo
cual utilizaremos el circuito integrado MAX232N.
Y para armar el circuito, nos guiaremos del siguiente esquemático, donde a pesar de
tratarse de un pic distinto, los pines 25 y 26 son coincidentemente iguales al del
dsPIC30f4013 para el UART1:
El conector DB-9 debe ser, en realidad, un cable conversor USB-Serial. En particular, he
logrado implementar el circuito con el cable HL-340.
Veamos ahora una programación sencilla para enviar un mensaje cada segundo desde el
dsPIC hacia la computadora.
Haremos el mismo procedimiento inicial que en el capítulo anterior, sólo que esta vez
incluiremos las siguientes líneas de código en el archivo clock.h:
#define BAUDRATE 9600
#define BRGVAL ((FCY/BAUDRATE)/16)-1
Estamos definiendo la tasa de Baudios como 9600 en la primera línea. Mientras tanto, la
segunda línea nos da el valor que debemos introducir a UxBRG.
#include "config.h"
#include "clock.h"
#include <libpic30.h>
#include "xlcd.h"
#include <stdio.h>
void UARTInit(void);                //función prototipo
void UARTInit(void){
       TRISFbits.TRISF3 = 0;               //salida TX para U1TX
     TRISFbits.TRISF2 = 1;               //entrada RX para U1RX
       U1BRG = BRGVAL;
       U1MODEbits.BRGH = 0;                //modo de baja velocidad
       U1MODEbits.UARTEN = 1;              //habilita el módulo UART1
}
int main(){
     UARTInit();    //inicializa el módulo                         UART     para     la
transmisión de datos en baja velocidad
              while(1){
                 printf("Texto de prueba.\r\n");
       //mostrar mensaje en consola
                     __delay_ms(1000);             //retraso de 1 segundo
              }
       return 0
}
Para poder observar el mensaje, es necesario una terminal. Se pueden utilizar los
siguientes software como ejemplos:
Terminal, Hyperterminal, Matlab y Arduino IDE.
En este caso, utilizaremos el monitor serial de Arduino. Siempre teniendo en cuenta que
el puerto COM sea el correspondiente al adaptador USB-Serial.
Ahora intentaremos enviar datos al dsPIC mediante el ordenador. Para ello, conectaremos
un diodo LED al pin RD8 como prueba.
Para comprender la correcta transmisión de datos, veamos nuevamente el registro
UxMODE:
Bit <2:1> PDSEL: bits de paridad y selección de datos
11 = datos de 9 bits, sin paridad
10 = datos de 8 bits, paridad impar
01 = datos de 8 bits, incluso paridad
00 = datos de 8 bits, sin paridad
Bit 0 STSEL: bit de detección de selección
1 = 2 bits de parada
0 = 1 bit de parada
Para propósitos prácticos y sencillos, utilizaremos datos de 8 bits sin paridad y 1 bit de
parada.
Y por cierto, esto no es todo. Debemos hacer uso de interrupciones. En particular,
utilizaremos los registros IFS0, IEC0 e IPC2. Para el último registro:
Bit 6-4 U1RXIP <2:0> bits de prioridad de interrupción del receptor UART1
111 = Prioridad de interrupción es 7 (la máxima posible)
.
.
.
001 = Prioridad de interrupción es 1
000 = Fuente de interrupción deshabilitada
También tengamos en cuenta los siguientes registros:
 UxRXREG: UARTx Receive Register
Almacena cualquier dato recibido. Cada vez que por consola o por terminal le envíe un
dato al dsPIC, este se almacenará en este registro.
 UxTXREG: UARTx Transmit Register (Write-Only)
Permite escribir datos y almacenarlos en este registro para poder transmitirlos.
Veamos ahora el código de ejemplo, donde el led en RD8 se encenderá cuando reciba un
1, y se apagará cuando reciba un 0. De igual manera, podemos enviar datos utilizando el
monitor serie de Arduino IDE.
#include "config.h"
#include "clock.h"
#include <libpic30.h>
#include "xlcd.h"
#include <stdio.h>
char dato = 0; //dato que se le enviará al dsPIC. Por defecto
//en 0
void UARTInit(void);                 //función prototipo
void UARTInit(void){
       TRISFbits.TRISF3 = 0;                 //salida TX para U1TX
      TRISFbits.TRISF2 = 1;               //entrada RX para U1RX
    U1BRG = BRGVAL;
    U1MODEbits.PDSEL = 0b00; //datos de 8 bits, sin paridad
    U1MODEbits.STSEL = 0;       //1 bit de parada
     IPC2bits.U1RXIP = 0b010; //prioridad        2    para     la
//interrupción
     IEC0bits.U1RXIE = 1;       //habilita la interrupción por
//recepción del UART1
     IFS0bits.U1RXIF = 0;     //borra       el       flag      de
//interrupción por recepción del UART1
    U1MODEbits.BRGH = 0;        //modo de baja velocidad
    U1MODEbits.UARTEN = 1;      //habilita el módulo UART1
}
int main(){
     UARTInit();    //inicializa el módulo       UART   para   la
//transmisión de datos en baja velocidad
    TRISDbits.TRISD8 = 0;       //pin RD8 como salida de datos
    LATDbits.LATD8 = 0;         //led encendido por defecto
         while(1){
                switch(dato){
                     case '0': LATDbits.LATD8 = 0; //apaga el
//LED en RD8
                     break;
                     case '1': LATDbits.LATD8 = 1;//enciende
//el LED en RD8
                     break;
                }
         }
    return 0;
}
void             __attribute__((__interrupt__,no_auto_psv))
_U1RXInterrupt(void){
     IFS0bits.U1RXIF = 0;     //borra       el       flag      de
interrupción por recepción del UART1
     dato = U1RXREG;    //la variable dato toma el valor
recibido y lo almacena.
}
Por ahora, esto es todo lo básico para la transmisión y recepción de datos mediante UART.
    Capítulo Extra I - ¿Cómo programar apropiadamente?
Hay una diferencia clara (o quizá no tan clara) entre conocer las sentencias, funciones y
comandos de un lenguaje de programación, y como utilizar esas herramientas para
estructurar un código que funcione de acuerdo a las necesidades presentes.
Hablando de forma totalmente personal aquí, me he encontrado con el caso de que mis
compañeros algunas veces (por no decir necesariamente «muchas») no saben cómo
enfrentarse a un problema.
Por ejemplo, supongamos que queremos encender 5 leds en forma escalonada: que se
encienda un led, luego otro y así sucesivamente, se apaguen todos y repita el ciclo. ¿Cómo
abordar este problema entonces? La forma apropiada y que cada estudiante debe tener
presente es la siguiente: estructurar todo el problema en etapas.
Primera etapa: configuración. Independientemente del lenguaje de programación que
estés utilizando, siempre comienza por incluir las librerías y dependencias necesarias (las
cuales ampliarás durante el transcurso de ser necesario). Para el caso de un
microcontrolador, también declara su configuración (incluyendo palabras reservadas por
el sistema como FCY [frecuencia de ciclo de instrucción]).
Segunda etapa: declaración de variables. Esta etapa no está más adelante por un hecho
simple: las variables siempre preferirás tenerlas luego de la configuración. Y aquí es
donde quiero que entiendan esto: no solo por referirme a etapas quiero decir que son pasos
a seguir tan solo, sino que todo forma parte de una estructura. Aquí iremos declarando las
variables que sean necesarias según avancemos con el código.
Tercera etapa: establecer el cuerpo principal del programa. El famosísimo “int main()”.
Básicamente, en esta función ocurrirá todo lo que sucederá realmente. Cualquier otra
función no será ejecutada hasta que esta sea llamada expresamente.
Cuarta etapa: establecer funciones. Dependiendo de la complejidad del problema,
podremos decantarnos por declarar funciones y realizar tareas de manera más ordenada.
La idea detrás de esto es poder mantener un control de flujo adecuado. Digamos que
estamos configurando el módulo ADC del dsPIC30f4013 y decidimos reciclar el código
para otro programa y es necesario cambiar alguna parte de los registros de control. Para
que dicha tarea antes mencionada sea sencilla de hacer, es mejor tener la configuración
del ADC separada en una función y no dentro del código principal. La cuestión es, como
mencioné antes, mantener un adecuado control de flujo.
Quinta etapa: ordenar cada tarea. Qué debería hacer primero ante un problema:
¿establecer los puertos como entradas o salidas? ¿Tener una variable que controle un
bucle? ¿Configurar un módulo UART o ADC? ¿Realizar una tarea con un periférico? La
idea de esta etapa, la cual considero la más importante, es derivar una parte del código
a realizar una tarea para que, en conjunto con otras tareas tras esta, puedan
llevarnos al funcionamiento requerido por el usuario.
                                 GLOSARIO
1. Testear: hacer pruebas.
2. Troubleshooting: solucionar problemas asociados a una máquina o sistema.
3. IO: Input Output; en español “Entrada Salida”.
4. Full-Duplex: la transmisión full-duplex (fdx) permite transmitir en ambas
   dirección, pero simultáneamente por el mismo canal. Existen dos frecuencias una
   para transmitir y otra para recibir. (Fuente: http://eveliux.com/mx/curso/modos-
   simplex-half-duplex-y-full-duplex.html)
5. Baudio: unidad de medida de la velocidad de transmisión de señales que se
   expresa en símbolos por segundo.