Unos de los sensores del
hexápodo va a ser un sensor para medir distancias por
ultrasonidos que se comunica con el microcontrolador mediante el
protocolo I2C, llamado TWI en los AVR.
Bus I2C.
Se trata de un protocolo serie desarrollado por Philips Semiconductors
usado por muchos integrados para comunicarse entre ellos, para su
funcionamiento requiere sólo dos líneas, una de reloj
(SCL) y otra de datos (SDA) junto a dos resitencias de pull-up con cada
una de estas líneas. Es un protocolo maestro esclavo en el que
el maestro inicia/termina la comunicación y debe generar
una señal de reloj (SCL), la línea de datos (SDA) es
bidireccional (el maestro puede mandar o recibir), por lo general
sólo suele haber un maestro (el microcontrolador) aunque el
protocolo soporta más de uno. Todos los circuitos integrados
conectados a este bus tienen una dirección física
distinta de la de los demás. Tansmisiones de 8 bits en
serie pueden ser realizadas a 100 kbit/s en el modo standar, 400 kbit/s
en el modo rápido y 3.4 Mbits/s en High/Speed mode.
Existen todo tipo de circuitos integrados con un bus I2C,
termómetros, memorias, relojes de tiempo real, drivers de
displays, etc.. y en nuestro caso el sensor de ultrasonidos que vamos a
utilizar entrega sus mediciones a través de este bus. Por lo que
conocer como funciona el bus y aprender a usarlo es interesante en esto
de la robótica.
En los AVR en protocolo I2C lo
encontramos con el nombre de TWI, nos permite conectar hasta 128
integrados al bus (límite capacitancia del bus 400 pF) usando
sólo dos líneas y añadiendo unas resistencias de
pull-up.
Las salidas de todos los dispositivos conectados al bus son en
colector-abierto, por lo que cualquier integrado puede mantener las
líneas a nivel bajo activando sus salidas.
Cada bit que se manda por la línea SDA va acompañado de
un pulso de reloj en la línea SCL, el valor del bit se toma en
la parte alta del reloj.
La condición de inicio de transmisión tiene lugar cuando
un integrado pone la línea de datos a cero estando la
línea de reloj a uno, el final de transmisión se indica
poniendo la línea de datos a uno con el reloj a uno. El maestro
se encarga de generar estas señales que detectaran los
integrados diseñados para I2C concetados al bus. Si el maestro
genera una señal de Start en lugar del Stop correspondiente
significa que va a seguir utilizando el bus e inicia otra
transmisión con otro esclavo.
Para seleccionar el esclavo con el que el maestro quiere hablar
después de la condición de Start, éste manda un
byte con los 7 primeros bits indicando la dirección del esclavo
(MSB el primero) y el último byte indica si va a mandar
información o a leer del esclavo, es un bit de
escritura/lectura. Después de cada byte siempre el receptor debe
generar un bit de reconocimiento, esta señal de reconocimiento
consiste en que el receptor pone la línea SDA a nivel bajo
después de recibir el último bit del byte enviado, si el
esclavo está ocupado o no puede recibir otro byte mantiene la
línea SDA a nivel alto y el master envia una señal de
Stop o una nueva señal de Start para iniciar otra
transmisión.
La dirección 00000000 está reservada para una llamada a
todos los esclavos, ésto se hace cuando el maestro quiere
transmitir el mismo mensaje a varios esclavos en el bus, evidentemente
una llamada a varios esclavos debe de indicar una operación de
escritura en el octavo bit.
El esclavo en una transmisión puede poner la línea de
reloj a cero si el tiempo de reloj generado por el master es demasiado
rápido para el esclavo, lo que hará disminuir la
velocidad de la transmisión.
El bus puede tener varios maestros pero en cada transmisión
sólo puede haber uno, cuando dos o mas maestros intentan iniciar
una comunicación al mismo tiempo hay un proceso de
selección para que sólo uno de ellos pueda coger el
control del bus, el resto de maestros deben cambiar a modo esclavo para
ver si el master que tiene el control quiere hablar con ellos como
esclavos. Los períodos de reloj que genera cada maestro deben de
estar sincronizados para facilitar el proceso de selección, de
esta forma la combinación de los distintos períodos de
los relojes tendra la duración del período de reloj del
maestro más lento en la parte baja del reloj (al ser las salidas
de cada dispositivo a las líneas en colector abierto el cero de
un dispositivo se impone al uno del otro) y la duración del
reloj más rápido en la parte alta. Para seleccionar que
maestro coge el control del bus estos miran la línea SDA, si el
valor de la línea SDA en cada ciclo alto de reloj no coincide
con el valor que ellos han puesto pierden el proceso de
selección (es decir cuando ellos quieran poner un 1 en SDA y
lean un 0).
En sistemas con varios maestros todas las transmisiones deben tener el
mismo número de paquetes (paquete = byte + bit ack) siendo el primero el byte de
dirección.
Módulo TWI en el AVR.
Está compuesto de varios submodulos:
SCL y SDA són los pines donde tenemos las líneas del bus,
se pueden activar las resistencias de pull-up internas del puerto evitando en algunas ocasiones tener que colocar las dos
resistencias de pull-up externas.
El "Bit rate generator" lleva el período del reloj cuando el
micro está funcionando como maestro, la frecuencia de reloj de
la CPU del esclavo debe ser de almenos 16 veces mayor que la frecuencia
de reloj que generamos en la línea SCL, el esclavo siempre pude
prolongar el tiempo de la parte baja del reloj si éste es
demasiado rápido.
La frecuencia de reloj en este módulo se genera atendiendo a la siguien ecuación:
Las resistencias del bus se deben elegir en función de esta frecuencia y de la capacitancia del bus:
Bus Interface Unit: contiene los datos a ser transmitidos o
recibidos, se encarga del bit de reconocimiento, el controlador de las
señales de Start y Stop y el hard para la selección de un
maestro cuando requieren el control del bus varios al mismo tiempo.
Address Match Unit: comprueba si los bits de dirección que se reciben son los nuestros.
Control: Unit: se encarga del control del bus.
El funcionamiento del módulo TWI está basado en
interrupciones que debe atender el micro, una ejemplo
transmisión sería algo así:, siendo TWINT el flag
de interrupción
1. El primer paso en toda transmisión es generar la señal
de Start, para ello se escribe un valor especifico en el registro TWCR
que indica al hardware generar la condición de Start, el bit
TWINT del registro TWCR se limpia y el hard comienza a
generar la condición de Start.
2. Cuando la señal de Start se ha transmitido el bit TWINT se
pone a uno y el registro TWSR es actualizado con un código/valor
que indica que se ha transmitido una condición de Start.
3. La aplicación comprueba el registro TWSR para ver que se ha
mandado el Start y carga la dirección del esclavo + el bit de
leer/escribir en TWDR y escribe un código en TWCR que indica al
hard que mande el valor contenido en TWDR. Se limpia el bit TWINT y se
inicia la transmisión.
4. Cuando se ha transmitido el paquete el bit TWINT se pone a uno y
TWSR es actualizado con un valor que indica que el byte se ha
transmitido de forma correcta reflejando el valor del bit de
reconocimiento.
5. El micro comprueba el registro TWSR y el valor del bit ack para ver
lo que ha sucedido en el paso anterior, si todo ha sucedido
según lo esperado se carga el byte de datos en TWDR, se escribe
en TWCR el código para que el hard lo mande y se limpia el bit
TWINT para iniciar la transmisión.
6. Cuando se manda el paquete de datos se pone a 1 el bit TWINT y se
actualiza el valor de TWSR con el resultado de la transmisión.
7. La aplicación comprueba TWSR para ver lo que ha pasado y
carga en TWRC el código para indicarle al hardware que mande la
condición de Stop una vez que la aplicación limpie el bit
TWINT. TWINT no es puesto a uno como resultado de mandar la
condición de Stop.
Resumiendo:
Cuando el módulo TWI ha finalizado una operación y espera
una respuesta del micro pone a 1 el bit TWINT, el reloj SCL está
a nivel bajo hasta que este bit es limpiado, se limpia escribiendo un
1 lógico en él.
Cuando TWINT se ha activado el micro debe actualizar los
registros del módulo con los valores necesarios para la
siguiente operación en el próximo ciclo del bus.
Una vez que se limpia TWINT el hardware ejecuta la operación establecida en los registros anteriores.
El módulo TWI tiene 4 modos de operación: tranmitr siendo
maestro (MT), recibir siendo maestro (MR), transmitir siendo esclavo
(ST) y recibir siendo esclavo (SR).
Master Transmitter Mode (MT). En este modo se tranmiten un número de bytes a un
esclavo, dependiendo del octavo bit del primer byte R/W se determina si se entra en este modo (MT) o en el modo MR
Los códigos a escribir en TWRC son los siguientes:
Cuando el bus esté libre se genera la señal de Start.
Para enviar un byte debemos escribir lo siguiente en el registro TWRC,
una vez que hayamos modificado el registro se limpia TWINT escribiendo
un uno, no antes.
El byte se pone en el registro TWDR y el resultado de la operación se guarda en TWSR.
Para generar la condición de Stop hay que escribir el siguiente código/valor en el registro TECR:
Cada vez que se realiza una operación cargada en TWCR se guarda
el resultado de la operación en TWSR, pudiendo obtener los
siguientes resultados:
Master Receiver Mode (MR).
En este modo el microcontrolador se encarga de generar la
señal SCL y leer los datos que un esclavo pone en SDA, los
códigos a escribir en TWCR para este modo son los siguientes:
Transmitir el byte de dirección con el bit de escritura/lectura
(en este caso debe ser lectura si no entramos en el modo anterior) y
leer sucesivos bytes:
Señal de Stop:
Los códigos son los mismos que en el caso anterior.
Los posibles códigos de TWSR:
Slave Receiver Mode (SR)
En este modo el micro no genera el reloj, se comporta como esclavo y lee datos mandados por un maestro.
Para poder entrar en este modo los registros TWAR y TWCR deben contener los siguientes valores:
En los 7 bits más altos tenemos la dirección que
identifica al microcontrolador en el bus I2C, si el bit TWGCE
está a 1 el micro responde a la dirección general 0x00,
si no ignorara una señal de Start en el bus seguida de esta
dirección.
Una vez que hemos escrito estos valores el módulo espera a una
señal de Start seguida del byte con su dirección, si el
bit de R/W es 0 el módulo funciona en este modo, si es 1 en modo
ST (transmite). Después de recibir cada byte se pone un
código de operación en el registro TWSR , si se resetea
el bit TWEA durante la transmisión el bit de reconocimiento que
debe generar el micro será un 0, indicando que no puede recibir
más bytes, mientras este bit esté a cero el módulo
no responde al bus I2C.
Los códigos según el resultado de la operación para el registro TWSR son los siguientes:
Slave Transmitter Mode (ST).
En este modo como en el anterior no se genera la señal de
reloj en SCL, los registros se deben configurar como en el caso
anterior siendo el bit de R/W del byte de dirección el que
selecciona entre un modo u otro. Si el bit TWEA es puesto a cero
durante la transmisión el módulo envia el último
byte y se desconecta del bus mientras el bit permanezaca en este
estado, si el master sigue leyendo de él siempre
encontrará un uno en la línea SDA.
Lós códigos que toma el registro TWSR según el resultado de cada acción son los siguientes:
Otros estados.
El estado 0x00 indica que ha ocurrido un error en el bus provocado por
porque ha tenido lugar donde no debe una señal de Stop o Start.
El bit TWINT se activa, para salir de este estado se debe limpiar este
bit junto a TWSTO.
Registros del microcontrolador.
Los registros del micro necesarios para realizar una comunicación mediante el bus I2C/TWI son los siguientes:
Estos bits son el valor del divisor de frecuencia utilizado para generar la señal de reloj en modo maestro.
Bit 7, TWINT (TWI Interrupt Flag): se activa por hardware para indicar
que el módulo TWI ha finalizado la tarea encomendada y
está a la espera de instrucciones del programa saltando a la
interrupción si está habilitada. Mientras este bit
esté a 1 el reloj SCL está a nivel bajo. Cuando se
ejecuta la interrupción TWINT no se limpia por lo que hay que
hacerlo por soft escribiendo un 1 en el bit, cuando lo limpiamos el
módulo TWI comienza la siguiente operación, determinada
por los registros TWAR, TWSR y TWDR que deden de ser modificados antes
de limpiar el bit.
Bit 6, TWEA: TWI (Enable Acknowledge Bit): si está a 1 el bit de
"reconocimiento" es generado si la dirección en el bus I2C del
micro o la 0x00 (si está habilitada) ha sido recibida o si un
byte de datos ha sido recibido en el modo MR o SR. Escribiendo un 0 en
este bit se desconecta el módulo TWI del micro del bus.
Bit 5, TWSTA: TWI (START Condition Bit): se pone a 1 cuando se
quiere ser maestro y generar la señal de Start cuando el bus
esté libre, se debe limpiar por software una vez que se ha
transmitido la señal de Start.
Bit 4, TWSTO: TWI (STOP Condition Bit): si se pone un 1 estando en modo
maestro se genera la señal de Stop, el bit se limpia por hard
cuando ésta ocurre. En modo esclavo se utiliza para salir de un
estado de error en el bus (0x00).
Bit 3, TWWC: TWI (Write Collision Flag): se pone a 1 si se
intenta escribir en TWDR estando TWINT a cero, este bit se limpia
cuando se escrie en el registro TWDR estnado TWINT a 1.
Bit 2, TWEN: TWI (Enable Bit): enciende o apaga el módulo TWI del microcontrolador.
Bit 0, TWIE: TWI (Interrupt Enable): habilita la
interrupción del módulo TWI, cuando TWINT se ponga a 1 se
lanza la ISR si ésta está habilitada.
Bits 7:3, TWS: TWI Status: indican el estado del módulo TWI según los distintos códigos anteriores.
Bits 1:0, TWPS: (TWI Prescaler Bits): controlar el prescaler.
En el modo de transmisión contiene el byte que se va a
transmitir y en el modo de recepción contiene el byte que se ha
recibido.
Bits 7:1, TWAM: (TWI Address Mask): indican la dirección del micro en el bus I2C.
Bit 0, TWGCE: si está a 1 el micro responde a la dirección "global" 0x00.
Bits 7:1 habilitan o deshabilitan los bits anteriores uno a uno.
Sensor SRF10.
El SRF10 es un sensor de ultrasonidos para medir distancias entre 3 cm
y 6 m, trae conexión a un bus I2C para comunicarse con
él, consumo entre 3 y 275 mA y una alimetación de 5
V. Su coste anda sobre los $60 por lo que para el robot hexápodo
es caro ya que habrá sensores de menor coste que se adapten
mejor a este tipo de robot (no necesitamos un rango de distancias de 6
m) pero lo tenía por aquí y viene bien para probar el bus
I2C del Arduino Mega.
Todo dispositivo conectado a un bus I2C deben tener una
dirección única, este sensor por defecto trae la
dirección 0xE0 (1000 0000 observar que el bit más bajo en
el byte de dirección es el de R/W, al asignar una
dirección del dispositivo ese bit siempre lo tomamos como cero,
no podemos tener una dirección como 0xE1 por ejemplo), nos
permite asignar al sensor una de las siguientes direcciones: E0, E2,
E4, E6, E8, EA, EC, EE, F0, F2, F4, F6, F8, FA, FC o FE, por lo que
podríamos conectar hasta 16 sensores de este modelo a un bus I2C.
El sensor y sus conexiones en la siguiente imagen:
El sensor funciona siempre como un esclavo, nunca hace de maestro y se
recomienda añadir un par de resistencias de pull-up de 1.8K para
su funcionamiento en las líneas del bus I2C.
El sensor tiene su propio microcontrolador, un pic16F, con el que
estableceremos la comunicación I2C. Tiene 4 registros:
El primer registro se utiliza para comenzar con las medidas obteniendo
el resultado en los registros 2 y 3, su significado depende del comando
utilizado para iniciar las medidas. Podemos escribir los siguientes
comandos en el primer registro:
Si escribimos el valor 80 (0101 0000) en el "Command Register" el valor
escrito en los registros 2 y 3 se corresponden con un valor en
pulgadas, de la misma forma podemos obtener los resultados en cm o uS
escribiendo los valores 81 y 82 en el command register.
Los otros tres valores que se pueden enviar al micro esclavo son
para seleccionar una dirección I2C del sensor entre las 16
posible, para realizar el cambio de dirección sólo se
puede tener conectado un sensor la bus (supongo que una vez cambiado se
puede conectar otro nuevo al bus y asignar su dirección) y se le
hace mandando los comandos anteriores en orden, por ejemplo si queremos
cambiar la dirección a la 0xF2 debemos escribir los siguientes
bytes en orden sobre el esclavo en 4 operaciones de escritura (es decir
con su señal de start y stop): 0xA0, 0xAA, 0xA5, 0xF2, la
dirección del sensor antes de cambiarla es la que viene por
defecto 0xE0.
Para iniciar una medición se ha de escribir uno de los 3
primeros valores en el registro de comandos y transurrido un tiempo se
podrá leer el resultado del esclavo, el tiempo por defecto es de
65 ms que se puede modificar escribiendo en el registro 2 (range
register) antes de iniciar la medición, si, el sensor
no responde a ningún comando mientras está realizando la
medición.
El tiempo de 65 ms se corresponde con un rango a medir de hasta 11
metros, para reducir la distancia máxima (no es capaz de
detectar objetos a mas de 6 metros) y reducir el tiempo que tarda
en hacerse una medición debemos escribir en el registro 2, un
incremento o decremento en este registro representa un paso de 43 mm,
es decir el rango máximo que se va a medir es el valor del
registro dos (range register)*43mm + 43mm, si escribimos un valor de
0x18 podemos detectar objetos a una distancia de un metro, la
única ventaja de disminuir el rango es que realizamos las
mediciones en menos tiempo. Cada vez que se enciende el sensor el rango
de medida se establece a 11 m y 65 ms.
El registro 1 (Max Gain Register) sirve para fijar el máximo
valor de la ganancia que amplifica la señal que se recibe o algo
así explica la hoja de datos que he encontrado del sensor que
deja mucho que desear.. Durante cada medición la ganancia
de cada etapa analógica (es decir el valor por el que
multiplicamos la diferencia de potencial (supongo) que crea el eco
recibido para que se entere el micro que ha llegado) empieza en su
valor mínimo 40, y cada 96 uS va aumentando al siguiente valor
de la tabla para detectar ecos producidos por objetos más
lejanos y por tanto más débiles, aumenta hasta el
máximo valor fijado en este registro. El propósito de
todo ésto es poder utilizar el sensor con tiempos menores de 65
uS, es decir si no queremos detectar objetos más lejanos de 1 m
de distancia y queremos realizar la medición lo más
rápido posible seleccionaremos un intervalo de tiempo menor
escribiendo en el registro 2, de esta manera detectamos por
ejemplo un objeto a 80 cm y nada más leer el valor de
distancia iniciamos otra medición, la onda generada sigue
viajando y rebota en un objeto a 6 m lo que puede hacer que nos afecte
a la segunda medición si recibimos el eco del objeto lejano
perteneciente a la primera medición antes que el eco del objeto
cercano perteneciente a la segunda medición. El eco del objeto
lejano es mucho más débil que el del objeto cercano, por
lo que si disminuimos el valos máximo de la ganancia, es decir
la sensibilidad a los ecos del sensor, podemos ignorar el débil
eco lejano evitando obtener lecturas falsas que indicaría un
objeto que no existe antes de los 80 cm.. La relación entre la
distancia máxima que queramos medir y la ganancia indicada para
esta distancia no es lineal, por lo que no hay una fórmula que
nos de una ganancia para un rango de distancias, el eco depende de la
geometría, orientación del objeto que tengamos delante.
Si no queremos preocuparnos de falsas medidas lo mejor es dejar los
valores de ganancia y rango como se quedan cuando se enciende el sensor
y no modificarlos haciendo mediciones cada 65 ms que para la
mayoría de los casos es suficiente.
Valores que establecen la ganancia máxima en el registro 1.
El led de la placa nos incida la dirección en el bus I2C del
sensor cuando encendemos éste y no recibe comandos I2C,
según las veces que se encienda y se apague tenemos las
siguientes direcciones:
Por últimos los objetos que detectará el sensor
siempre que estén colocados de maneta perpendicular al
eco (si les pilla de lado el ángulo de sálida es el
de incidencia por lo que podemos no detectar un objeto que tengamos
delante) deben de estar en este cono:
Por lo que para evitar ecos producidos por rebotes una opción es
disminuir la ganancia máxima, si queremos detectar objetos a 1 m
de distancia pues probablemente no tenga sentido usar la máxima,
a no ser que éstos sean muy pequeños.
Resumiendo la hoja de datos que he encontrado.. para escribir un
comando mandamos la señal de Start, la dirección del
sensor en el bus indicando escritura, un byte con el número de registro en el que vamos a escribir para seleccionar el
registro interno, el comando a escribir y la señal de Stop para
finalizar.
Para leer mandamos la señal de Start, un byte con la
dirección i2c del sensor en el bus indicando que vamos a
escribir,
un byte con el número de registro en el que vamos a comenzar a
leer y una señal de Stop. A continuación mandamos una
nueva señal de Start, un byte con la dirección i2c del
sensor
indicando que vamos a leer y comenzamos la lectura en el bus, una vez
leído el primer registro se pasa de manera automática a
leer el segundo finalizando con una señal de Stop, no mandando
antes del Stop el byte de ack en el último byte recibido para
indicarle al esclavo que vamos a finalizar. Ejemplo de uso de todo lo anterior.
Un programa simple para comprobar el funcionamiento del bus I2C y del
sensor SRF10, se va a leer el sensor y sacar la medición por el
LCD de manera continua. Para ello primero debemos activar el
módulo TWI en el micro para poder configurar el sensor, iniciar
la medición y leer el resultado.
Nuestro microcontrolador va a funcionar como maestro y el sensor como
esclavo por lo que debe generar la señal de reloj, lo primero es
determinar la frecuencia de la señal y debemos atender a la
siguiente tabla para ver los requisitos del bus I2C:
Vamos a seleccionar los requisitos de una frecuencia de reloj menor de
100 KHz, para ver cuál es la velocidad máxima a la que
podemos intercambiar datos habría que mirar el datasheet del
pic16f en el sensor para ver sus requisitos ya que información
detallada sobre el sensor hay poca por no decir ninguna, o almenos yo
no la he encontrado. En la tabla vemos un tiempo mínimo para la
parte alta de SCL de 4 us y para la baja de 4.7 us (el esclavo puede
hacer que sea más largo si es muy rápido para él
poniendo la línea de reloj a cero), además están
los tiempos para las señales de Start, Stop, el tiempo del bit,
etc..
La frecuencia a la que va a funcionar nuestro reloj la determinamos según la siguiente fórmula:
El reloj externo del micro (CPU Clock frequency) es de 16 MHz por lo
que si dejamos los registros TWBR y TWPS a 0 tendríamos una
frecuencia de 1MHz, es decir la señal de reloj tiene un
período de 1 us por lo que no nos vale, como mínimo
necesitamos un período de 10 us (5us en la parte alta + 5 us en
la parte baja), vamos a generar un período de reloj de 20 us,
para ello debemos escribir en TWPS el valor 0 (son dos bits) y en TWBR
el valor 152, por lo que la frecuencia según la fórmula
es la siguiente, SCLf = (16000000)/(16+2*(152)*1) = 50KHz, es decir el
período de 20 us.
Luego en el registro TWBR escribimos el valor 10011000 y en los bits
TWSP1 y TWSP0 del registro TWSR el valor 0, de esta formas configuramos
una señal de reloj en la línea SCL de 20 us que el micro
genera cuando funcione como maestro. En el registro TWCR activamos los
bits TWEA y TWEN para conectar el módulo al bus y habilitar a la
línea. TWAR y TWAMR sirven para asignar la dirección
cuando podemos ser esclavo, en este caso solo vamos a ser maestros, con
TWIE de TWCR y el bit GIE de SREG del mismo registro habilitamos la
interrupción que usaremos durante las transmisiones. Cada vez
que se ejecute una operación del módulo se activa el flag
de interrupción TWINT y se salta a la ISR, ISR(TWI_vect); en
este caso. La transmisión la podemos hacer mediante
instrucciones o sin ellas haciendo al micro esperar hasta que se active
el flag anterior, lo más práctico es usar interrupciones
y dejar al micro ejecutar otro código en el tiempo de la
transmisión, para probar el sensor se va a usar el ejemplo más simple que es sin interrupción.
Para que el micro hable con el sensor se van a crear dos funciones, una para escribir y otra para leer:
while (!(TWCR & (1<<TWINT))) //Esperar a TWINT
{
}
//comprobar condición.
if ((TWSR & 0xF8) !=
0x28)
//(xxxxx000) != (0x28 dato+ack)
i2c_error();
}
TWCR &= ~(1<<TWSTA);
//enviar señal de stop.
TWCR |= ((1<<TWINT)|(1<<TWEN)|(1<<TWSTO));
_delay_us(20);
}
Conectando un analizador lógico a la línea I2C se observa
la siguiente salida cuando se ejecuta está función, los
parámetros que se le pasan son 0xE0 y dos bytes a escribir 0x00
y 0x51:
La primera parte del código es la señal de Start y el primer byte de direccióny
se corresponde con el comienzo de la imagen en el bus i2c, el
período de la señal es de 20 us como se configuro en
TWBR, los otros bits del prescaler TWPS1:0 están a cero
por defecto:
TWCR |=
(1<<TWEA);
//Se
enciende el módulo
TWCR &= ~(1<<TWSTO);
//enviar
señal de start.
TWCR |= ((1<<TWINT)|(1<<TWSTA)|(1<<TWEN));
while (!(TWCR &
(1<<TWINT)))
//Esperar a
TWINT
{
}
//comprobar resultado
de la operación anterior.
if ((TWSR & 0xF8) != 0x08)
//(xxxxx000) != (0x08 Start)
i2c_error();
Lo primero es generar la señal de Start, ésta tiene lugar
cuando estando la línea de SCL a 1 la línea de SDA(la de
arriba) pasa de 1 a 0, para ello ponemos los bits del registro TWCR con
sus valores correspondientes para indicar el START y limpiamos TWINT
escribiendo un uno lógico en él, ésto indica al
módulo TWI del micro que tiene que ejecutar una acción
determinada por el resto de bits del registro. Con while (!(TWCR & (1<<TWINT))) esperamos
a que el bit TWINT del registro TWCR se ponga a 1 para indicarnos que
ya se ha enviado la señal de Start, cada acción que
realiza el módulo TWI indica su resultado en los 5 bits
más altos del registro TWSR, por lo que enmascaramos estos bits
y leemos su estado comparandolo con el valor 0x08 que nos pone el
módulo TWI en estos bits para decirnos que una señal de
Start se ha transmitido. Ésto es un ejemplo sencillo y se usa un
while para esperar al bit, el micro se queda ahí parado sin
hacer nada, en el programa completo habrá que usar la
interrupción en lugar del búcle de espera. Si no
recibimos el valor 0x08 salimos de la función generando una
señal de Stop en la función i2c_error();
Lo siguiente es mandar el byte de dirección con el bit de escritura y lectura:
//enviar byte.
TWDR = ((direccion)&(0b11111110));
//Cargar byte a enviar
TWCR &= ~((1<<TWSTO)|(1<<TWSTA));
TWCR |= ((1<<TWINT)|(1<<TWEN));
while (!(TWCR &
(1<<TWINT)))
//Esperar a TWINT
{
}
//comprobar
condición.
if ((TWSR & 0xF8) != 0x18)
//(xxxxx000) != (0x18 SLA +W)
i2c_error();
En TWDR cargamos la dirección de nuestro esclavo 0xE0 y la
pasamos por una AND para que el bit más bajo el de R/W valga
siempre 0 indicando escritura. El bit se lee en la parte alta del
reloj, por lo que la imagen se puede leer el valor: 11100000 es los 8
pulsos de reloj, es decir 0xE0 que hemos mandando, sin embargo el
analizador nos indica que hemos mandado la dirección 0x70 y
ésto es asi porque cuenta sólo 7 bits de dirección
1110000 (0x70) y el bit más bajo de lectura/escritura
independiente de la dirección, por lo que la dirección
del bus no se corresponde con la dirección que indica los
sensores, en los sensores se han añadido este bit de R/W de
más al nombrar las direcciones.
En el pulso 9 de reloj el esclavo debe poner la línea SDA a 0
para mandar el bit de ack, se puede observar como nada más
finalizar la parte alta del pulso 8 se produce un pequeño pulso
en SDA, el master pone a 1 la línea (la libera en su open
collector) e inmediatamente el esclavo la pone a cero para indicar que
ha recibido el dato y que se le pueden mandar más, en la parte
alta del 9 pulso se leer el bit de reconocimiento. El otro pulso
pequeño que se ve seguido al anterior es porque se manda otra
byte a continuación y su primer bit vale 0 (no tiene
importancia).
Como en el caso anterior esperamos a que el bit TWINT se ponga a 1
indicando que el módulo TWI ha realizado la operación que
le hemos indicado y comprobamos el estado de ésta leyendo los
bits de TWSR, el valor 0x18 lo ha puesto el módulo TWI indicando
que ha mandado la dirección y que ha recibido un bit de ack por
parte del esclavo.
Siguiendo los mismos pasos se mandan dos bytes más de datos del
maestro al esclavo y se genera una señal de Stop (SDA pasa de 0
a 1 con SCL a 1) para finalizar la transmisión y dejar libre el
bus I2C, el módulo TWI nos devuelve el código 0x28
indicando que el dato se ha mandado y el esclavo ha generado el bit de
reconocimiento.
while (!(TWCR & (1<<TWINT))) //Esperar a TWINT
{
}
//comprobar condición.
if ((TWSR & 0xF8) !=
0x28)
//(xxxxx000) != (0x28 dato+ack)
i2c_error();
}
TWCR &= ~(1<<TWSTA);
//enviar señal de stop.
TWCR |= ((1<<TWINT)|(1<<TWEN)|(1<<TWSTO));
_delay_us(20);
Después de la señal de
Stop hay que generar un pequeño delay (4us) para que ésta
sea detectada, ya que el bit TWINT del módulo no se pone a 1
después de generar un STOP para indicar que se ha realizado.
La siguiente función se utiliza para leer en el sensor, para
ello primero hay que escribir un valor en el esclavo para seleccionar
el registro que vamos a leer y a continuación comenzar a leer
bytes. Se podía haber usado la función anterior para la
primera parte de la escritura.
La anterior función produce la siguiente salida en el bus:
Entre la primera señal de start y stop tenemos el mismo
código que la función anterior, nuestro micro es master y
escribe en el bus, seleccionamos la dirección del esclavo y
escribimos el valor del registro del sensor (tiene 4) sobre el que
vamos a realizar la operación de lectura/escritura, una vez que
realizamos una operación el "contador" de registro del sensor se
incrementa permitiendonos escribir o leer directamente en el siguiente
registro sin tener que seleccionarlo. En este caso seleccionamos el
registro 2 que según indica la documentación guarda la
parte alta del resultado y lo leemos, a continuación hacemos
otra operación de lectura directamente ya sobre el registro 3
del sensor sin tener que seleccionarlo, si volvemos a hacer otra
operación a continuación al no haber más regitros
lo haremos sobre el 3 también.
Entre el Stop y el Start hay que poner un delay para que establezcan las señales y puedan ser detectadas en el bus.
En cuanto a la parte del micro como maestro leyendo de un esclavo, se
genera la señal de Start como en los casos anteriores, y se
procede a mandar la dirección pero esta vez con el bit de R/W a
1:
while (!(TWCR & (1<<TWINT))) //Esperar a TWINT
{
}
//comprobar condición.
if ((TWSR & 0xF8) != 0x40)
//(xxxxx000) != (0x40 SLA +R)
i2c_error();
El módulo nos devuelve el código 0x40 indicando que todo se ha realizado según los esperado.
Pra leer un byte y alamcenarlo en un registro de nuestro micro escribimos el siguiente código:
TWCR &= ~((1<<TWSTO)|(1<<TWSTA)); //Leer primer byte
TWCR |= ((1<<TWINT)|(1<<TWEN)); while (!(TWCR & (1<<TWINT))) //Esperar a TWINT
{
}
El byte que recibimos lo tenemos en TWDR y obtenemos el valor 0x50 que
indica que todo ha sucedido según lo esperado. Para leer el
segundo y último byte lo hacemos de la misma forma, pero esta
vez tenemos que indicar al módulo TWI que no genere el bit de
reconocimiento cuando reciba el byte para que el esclavo libere la
línea y podamos generar la señal de Stop, para ello
ponemos a cero el bit TWEA de TWCR. TWCR &=
~((1<<TWSTO)|(1<<TWSTA)|(1<<TWEA));
//Leer segundo byte NO ACK
TWCR |= ((1<<TWINT)|(1<<TWEN));
while (!(TWCR & (1<<TWINT))) //Esperar a TWINT
{
}
if ((TWSR & 0xF8) != 0x58)
//(xxxxx000) != (0x58 dato+ no
ack)
i2c_error();
bytes_i2c[2]=TWDR;
Recibimos el valor 0x58 que indica que hemos recibido el byte y no se
ha generado el bit ack, guardamos el byte y generamos la
condición de Stop para dejar libre el bus.
No adjunto el código completo porque no sé si mi sensor
está bien, en teoría y según la
documentación que he sido capaz de encontrar del SRF10 el
resultado de la medida se guarda en el registro 2 y 3 pero en el
registro 2 siempre tengo el valor 0x00, no sé el porque. La
verdad que la única documentación que he encontrado es la
que hay en la página de superrobótica y el datasheet en
inglés en otras páginas de robótica, que es de
donde se ha traducido lo de superrobótica. No sé si
habrá más info, ta haré una sesión de
google.
Mi while es el siguiente: while(1)
{
bytes_i2c[0]=0x00;
bytes_i2c[1]=0x51;
i2c_escribir(0xE0, bytes_i2c, 2);
En el registro 3 los valores se incrementan y decrementan correctamente
con la distancia, pero en el 2 0x00 siempre, y si aplico los 4.3 cm por
bit sobre los 255 valores del registro 3 me salen distnacias que no se
corresponden, por lo que no sé si tengo el sensor roto. Pero
bueno para el ejemplo del funcionamiento del bus I2C en el Arduino me
vale ya que responde a las funciones, ya volveré a mirar
más adelante el tema del sensor bien y hacer las funciones
mediante la ISR que de momento me urge el tema del AVR que tengo un 3pi
para destripar y aplicar lo aprendido a un miniz con AVR =).