Rutinas para conectar dispositivos I2C a PICs sin bus serie

De Asociación de Robotica y Domótica de España (A.R.D.E.)
Saltar a: navegación, buscar

IMPLEMENTACIÓN DE RUTINAS I2C PARA MICROCONTOLADORES 16F8X

Autor: The-Next

Bueno, este trabajito lo hice en mi primer contacto con los microcontroladores. El objetivo era añadirle a un microbot que usaba el 16F84 unos cuantos dispositivos que funcionaban a través del bus I2C. Evidentemente este micro, a diferencia de otros mas avanzados no dispone de funciones especiales para la comunicación serie así que se tuvo que diseñar una.


Antes de ponernos al meollo hay que concretar que el algoritmo que escribí no contempla todas las funcionalidades de dicho bus. Existen dos aspectos de la comunicación I2C que no se han implementado ya que no han hecho falta. La primera es el tema del Arbitraje. El 16F84 iba a ser la única unidad principal por lo que no hace falta ningún tipo de arbitraje. El segundo aspecto no implementado son los estados de espera. Todos los dispositivos I2C con los que trabajamos eran lo suficientemente rápidos por lo que tampoco ha habido necesidad de que el 16F84 se preocupara de ello. De todas formas, si son necesarios alguno de los dos supongo que no será muy difícil modificar el algoritmo que voy a enseñar.


Los dispositivos a los que accedíamos con este bus eran una memoria EEPROM, una brújula digital y un sensor de ultrasonidos.


Primeramente, una de las características que tiene el bus I2C es que las líneas son en colector abierto, es decir, el nivel alto realmente es alta impedancia, por lo que se necesitan 2 resistencias conectadas a VCC para que pongan estas líneas a nivel bajo Esto tiene la ventaja, de que si un elemento pone nivel alto y otro deja nivel bajo, en la línea se lee nivel bajo, pudiéndose detectar situaciones que veremos más adelante.

Error al crear miniatura: No se ha podido guardar la miniatura

Así que en vez de conectar el bus directamente a las patillas del PIC tuvimos que pasarlas antes por un inversor en colector abierto y poner dos resistencias de pull-up:


El 16F84 estaba conectado al “picbus”, del cual las lineas correspondientes a las patillas RB1 y RB4, que eran utilizadas para la señal de reloj y de datos se pasaban por el inversor en colector abierto. La patilla RB5 se utilizaba para leer del bus los datos.


El esquema de lectura de un registro de un dispositivo atendía al siguiente esquema (ejemplo de la lectura de la orientación de un compás digital):

Error al crear miniatura: No se ha podido guardar la miniatura

Esta es la secuencia de procedimientos para la lectura:

- El 16F84 Genera la secuencia de Start. A continuación Manda la dirección de la brújula (7 bits) y finalmente un 0 para indicar que desea escribir.

- El 16F84 Deja SDA en nivel alto y entonces el compás digital realiza el ACK (poniendo “0” en la línea).

- A continuación el microcontrolador manda el registro de la brújula al que quiere acceder y el compás le responde con un ACK.

- El 16F84 genera una secuencia de Restart, manda la dirección de la brújula ( 7 bits) y un 1 indicando que desea leer.

- El 16F84 Deja SDA en nivel alto y entonces el compás digital realiza el ACK.

- A partir de este momento El PIC mantiene SDA a nivel alto y comienza a leer los datos. Cuando haya leído los bytes deseados generará la secuencia de Stop y la comunicación habrá finalizado.


A continuación vamos a mostrar y explicar el código ensamblador que realiza la lectura de un registro de un dispositivo I2C.

Antes de empezar sería recomendable tener en cuenta dos cosas.


1º La placa implementada para conectarse con los dispositivos I2C lleva un inversor por lo que los impulsos generados por el PIC serán justo lo contrario que los de la gráfica.


Lo que decidimos en un primer lugar es crear una serie de subrutinas para cada acción, secuencia de inicio, enviar byte, ect... Las rutinas son las siguientes (configuración necesaria del PIC incluida):

INCLUDE     "Macros.inc"            ;Inclusi¢n de macros.
 
list p=16f84
 
 
org 0
 
 
;----------------------------------- I2C---------------------------------------------------------
;----------------------------------- I2C---------------------------------------------------------
 
;vamos a utilizar rb4 como se¤al de reloj , rb1 como puerto de escritura de datos , rb5 como lectura de DATOS
 
;RB1 Escritura SDA
;RB2 Lectura de nada
;RB4 Escritura SCL
;RB5 Lectura SDA
 
 
; ATENCION LOS COMENTARIOS ESTAN AL REVES YA QUE SE PASA POR UN INVERSOR EN CONECTOR ABIERTO, POR LO QUE LAS SALIDAS
; QUE PONE EL PIC LUEGO SE INVIERTEN!!!!
 
 
; RB4 ---> Inversor ---> SCL
 
; RB1 ---> Inversor ---> SDA
             |
; RB5  <-------------- /
 
;----------------------------------- I2C---------------------------------------------------------
;----------------------------------- I2C---------------------------------------------------------
 
 
 
bsf 0x03,5 ;pasamos al banco de memoria 1
bsf 0x0b,7
movlw b'11101001'      ; configuramos en el registo option las resistencias pull-up inactivas , Tmr0 aunmenta con el t0ck1
movwf 0x01           
movlw 0x00
movwf 0x06                              ; configuramos RB como salidas
bsf 0x06,5                                                              ; RB5 como entrada
bsf 0x06,2                                                              ; RB2 como entrada
bsf 0x06,3                                                              ; RB3 como entrada
bsf 0x06,0                                                              ; RB0 como entrada
bsf 0x06,6                                                              ; RB6 como entrada
bsf 0x06,7                                                              ; RB7 como entrada
 
 
movlw 0x00
movwf 0x05                              ;configuramos RA como salidas
bsf 0x05,4                                                                           ;RA 4 entrada
bcf 0x03,5             ;pasamos al banco de memoria 0
 
movlw 0x00
movwf 0x06                         ;ponemos a nivel bajo todos los RB ( RB1 y RB4 a "1")
 
 
 
 
;----------------------------------------------------------------------------------------
;----------------------------------------------------------------------------------------
;------------------------------- MANEJO DE  I2C ---------------------------------
;----------------------------------------------------------------------------------------
;----------------------------------------------------------------------------------------
 
 
 
;---------------------------------------------------------------------
;------------------------------ MEOLLO DEL ASUNTO!!! -----------------
;--------------- se parte con las lineas SDA y SCL a 0 logico---------
;---------------------------------------------------------------------
 
 
i2csec; Manda un byte almacenado en el registro 0x10 por el bus i2c
         ; Usamos el registro 0x0f para guardar la respuesta del ack
bcf 0x0f,0;
 
 
BSF 0X06,1                      ;Pone a 0 SDA
BTFSC 0X10,7 ; Salta si el bit X de 0x10 esta a cero
BCF 0X06,1;                    ;pone a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
BSF 0X06,1                      ;Pone a 0 SDA
BTFSC 0X10,6 ; Salta si el bit X de 0x10 esta a cero
BCF 0X06,1;                    ;pone a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
BSF 0X06,1                      ;Pone a 0 SDA
BTFSC 0X10,5 ; Salta si el bit X de 0x10 esta a cero
BCF 0X06,1;                    ;pone a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
BSF 0X06,1                      ;Pone a 0 SDA
BTFSC 0X10,4 ; Salta si el bit X de 0x10 esta a cero
BCF 0X06,1;                    ;pone a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
BSF 0X06,1                      ;Pone a 0 SDA
BTFSC 0X10,3 ; Salta si el bit X de 0x10 esta a cero
BCF 0X06,1;                    ;pone a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
BSF 0X06,1                      ;Pone a 0 SDA
BTFSC 0X10,2 ; Salta si el bit X de 0x10 esta a cero
BCF 0X06,1;                    ;pone a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
BSF 0X06,1                      ;Pone a 0 SDA
BTFSC 0X10,1 ; Salta si el bit X de 0x10 esta a cero
BCF 0X06,1;                    ;pone a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
BSF 0X06,1                      ;Pone a 0 SDA
BTFSC 0X10,0 ; Salta si el bit X de 0x10 esta a cero
BCF 0X06,1;                    ;pone a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
bcf 0x06,1       ;ponemos a 1 SDA
call retardo    
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5    ;salta si se ha recibido el ack
bsf 0x0f,0;            ;COMPROBAMOS EL ACK
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo
 
 
; La rutina finaliza con SCL a 1 y SDA a 1
return
 
;----------------------------------------------------------------
; La siguiente rutina obtiene 2 bytes leidos del bus i2c, en caso de la brujula solo te toma  en cuenta el primer byte
; Los bytes usados seran 0x0C y 0x0D    --- BYTE DE MAYOR PESO 0X0D
;----------------------------------------------------------------
 
 
i2crec
clrf 0x0c;
clrf 0x0d;
 
call retardo    ; Leemos los datos y los almacenamos en 0x0d
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0d,7
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0d,6
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0d,5
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0d,4
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0d,3
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0d,2
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0d,1
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0d,0
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
bsf 0x06,1       ;ponemos a 0 SDA, ACK
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
call retardo
bsf 0x06,4               ; ponemos a 0 SCL
bcf 0x06,1       ;ponemos a 1 SDA
 
call retardo    ; Leemos los datos y los almacenamos en 0x0c
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0c,7
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0c,6
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0c,5
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0c,4
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0c,3
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0c,2
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0c,1
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
btfsc 0x06,5
bsf 0x0c,0
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
bsf 0x06,1       ;ponemos a 0 SDA, ACK
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
call retardo
bcf 0x06,1       ;ponemos a 1 SDA                                   Bit de stop
call retardo
 
; La rutina finaliza con SCL y SDA a 1
return
 
 
;----------------------------------------------------------------
;----------------------------------------------------------------
;--------Subrutinas peque¤as a las que se llaman desde arriba----
;----------------------------------------------------------------
;----------------------------------------------------------------
 
 
paro
; genera la secuencia de stop
 
call retardo
bsf 0x06,4               ; ponemos a 0 SCL
bsf 0x06,1       ;ponemos a 0 SDA
call retardo 
bcf 0x06,4               ; ponemos a 1 SCL
call retardo
bcf 0x06,1       ;ponemos a 1 SDA
return
 
 
inicio
; Genera una secuencia de inicio
 
bsf 0x06,1       ;ponemos a 0 SDA
call retardo    
bsf 0x06,4               ; ponemos a 0 SCL
 
; Sda y scl quedan a 0
return
 
 
reinicio
; Genera un bit de reinicio, scl debe estar a cero
 
 
bcf 0x06,1       ;ponemos a 1 SDA
bcf 0x06,4               ; ponemos a 1 SCL RE-INICIO
call retardo
bsf 0x06,1       ;ponemos a 0 SDA
call retardo
bsf 0x06,4               ; ponemos a 0 SCL
call retardo
 
; Scl y sca quedan a cero
return
 
 
retardo  ;espera 10 microsegundos ( 5 us del cambio de contexto y otros 5 de las nops)
 
 
 
nop
nop
nop
nop
nop
return

Con estas subrutinas implementadas, el comunicarse con un dispositivo es relativamente fácil. Lo que hice ahora fue hacer otras subrutinas que trabajaban sobre las que he implementado de forma que al final de todo solo tuviera que llamar a 1 subrutina para realizar toda la secuencia:

Secuencia de lectura del registro 01 de la brújula:

; ----------  ( BRUJULA) -----------
 
brujula
 
iniciob   
call retardo
 
call inicio
 
movlw 0xc0;
movwf 0x10;             PONEMOS c0 en 0x10
call i2csec;
btfsc 0x0f,0;
goto errorb
call retardo
 
 
movlw 0x01;
movwf 0x10;             PONEMOS 01 en 0x10
call i2csec;
btfsc 0x0f,0;
goto errorb
call retardo
 
 
call reinicio
 
 
movlw 0xc1;
movwf 0x10;             PONEMOS c1 en 0x10
call i2csec;
btfsc 0x0f,0;
goto errorb
call retardo
 
call i2crec;
 
return
 
 
errorb
call paro
goto iniciob


Secuencia de lectura y secuencia de escritura para leer de una memoria EEPROM, en nuestro caso era una tarjeta chip.


chipescritura
 
call inicio
 
 
movlw 0xa0
movwf 0x10;             PONEMOS a0 en 0x10 (a0 es la dirección de la memoria EEPROM)
call i2csec;
btfsc 0x0f,0;
goto stop3
call retardo
 
 
movfw 0x0d
movwf 0x10;             PONEMOS lo que hay en 0x0d en 0x10 ( la dirección del registro estará almacenada en 0x0d)
call i2csec;
btfsc 0x0f,0;
goto stop3
call retardo
 
 
movfw 0x0c
movwf 0x10;             PONEMOS lo que hay en 0x0c en 0x10 ( el dato que queremos guardar estará en 0x0c )
call i2csec;
btfsc 0x0f,0;
goto stop3
call retardo
 
 
call paro
return
 
 
stop3
call paro
goto chipescritura
 
 
 
chiplectura
 
call inicio
 
movlw 0xa0
movwf 0x10;             PONEMOS a0 en 0x10
call i2csec;
btfsc 0x0f,0;
goto rein
call retardo
 
 
movfw 0x0d
movwf 0x10;             PONEMOS lo que hay en 0x0d en 0x10 (direccion del registro que queremos leer)
call i2csec;
btfsc 0x0f,0;
goto rein
call retardo
 
call reinicio
 
movlw 0xa1
movwf 0x10;             PONEMOS a1 en 0x10
call i2csec;
btfsc 0x0f,0;
goto rein
call retardo
 
call i2crec
 
return
 
 
rein
call paro
goto chiplectura


Finalmente, en nuestro programa principal simplemente tenemos que llamar a estas rutinas y tendremos los valores deseados de los dispositivos en los registros indicados. Para terminar pongo una pequeña lista de los registros que utilicé:

0X10 → Se almacena el byte a enviar por el bus I2C

0X0F → Se utiliza para recibir los ACK de los dispositivos

0X0C → Se almacena el 2º byte recibido por el bus I2C
     → Se almacena el dato que se quiere escribir en la tarjeta chip
 
0X0D → Se almacena el 1º byte recibido por el bus I2C
     → Se almacena la dirección del registro de la tarjeta chip a la que se quiere acceder


Bueno, eso es todo, creo que ésta guía puede ser útil para utilizarla en pequeños microcontroladores que no tengan implementadas por hardware las funcionalidades del bus I2C. Espero que os sirva.

No he explicado tal vez mucho el código, si tenéis alguna duda podéis consultarme, si me acuerdo xd que hace año y medio que escribí el código este.