I2C ATtiny85 Maestro y Esclavo (AVR TWI – Two Wire Interface)

I2C, IIC, AVR TWI, Two Wire Interface, Inter Integrated Circuit, dos cables…

Esta caracteristica es la peor documentada en el datasheet del ATtiny85 y debido a eso varios post por internet que hablan del tema tienen codigo redundante, con llamadas o escrituras innecesarias y hacks mal implementados; Ademas la mayoria solo se limita a utilizar la misma biblioteca… Cosa que aqui no usaremos.

Con osciloscopio ‘en mano’ me di a la tarea de analizar el comportamiento de los micros configurandolos como maestro y esclavo comunicando dos ATtiny’s entre ellos; Por lo tanto la idea para este post es implementear la funcionalidad de TWI sin una biblioteca third-party (programando a pata todo en C) y publicar un template solido, compacto y lo mas generico posible para que se pueda usar en cualquier proyecto.

Entendiendo el TWI en los ATtiny85 (y casi cualquier Tiny)

La configuracion para que un Tiny se desempeñe como maestro o esclavo no es directamente seleccionable, esto es importante saberlo por que quiere decir que dentro de la misma logica del codigo y dependiendo de TODO el conjunto de las acciones programadas ‘libremente’ es lo que define al dispositivo como uno u otro.

En el diagrama anterior se muestra la comunicacion I2C de donde podemos definir lo siguiente:

  • La conexion entre maestro y esclavo es evidentemente solo con dos cables, SDA o linea de datos (PB0 pin 5) y la linea del clock o SCL (PB2 pin 7); La conexion es 1 a 1: SDA con SDA y SCL con SCL de cada uno de ellos.
  • El unico que controla la linea del clock es el maestro y el unico que puede mandar ACK es el esclavo. Por lo que ambos estan constantemente intercambiando el control solo de la linea SDA.
  • La señal de start (A) la da el maestro, si en el codigo ambas lineas (SDA y SCL) se configuran como salida mediante DDRB y se ponen en alto mediante PORTB previamente a la activacion de la TWI entonces la condicion de start se hara de forma AUTOMATICA inmediatamente despues de la activacion de la TWI, esto se tomara en cuenta en el codigo de ejemplo para el maestro.
  • La address de los esclavos (C) es solo de 7-bits por lo que el 8vo se utiliza para que el maestro declare su tipo de instruccion donde 1 = lectura y 0 = escritura.
  • Si el address hace match con la perteneciente a algun esclavo entonces este debe tomar el control de la linea SDA momentaneamente y mandar un pulso LOW en el tiempo de ACK para que el maestro pueda determinar si fue recibido su mensaje.
  • Una vez que el maestro ha recibido un ACK mandara/requerira su informacion en trenes de pulsos de 8 bits lo cual en caso de ser necesario se podria hacer multiples veces siempre con un ACK entre cada uno de ellos.
  • El maestro siempre debera mandar una señal de stop (F) como ultima accion para finalizar la comunicacion correctamente.

Consideraciones sobre algunas configuraciones:

-En el datasheet se menciona que el Timer/Counter 0 puede ser utilizado para la señal de clock, pero a la vez de forma oscura menciona que la señal en SCL debera ser generada siempre de forma manual por lo que si se pretende utilizar el Timer/Counter 0 entonces este solo se encargara de enviar los datos con el ritmo de clock adecuado pero NO hara ningun cambio en las señales de la linea SCL… Como lo dije anteriormente sigue siendo necesario hacer esa señal de forma manual, lo cual es raro y lo desaconsejo usar totalmente tanto por economia de codigo como para evitar desincronizaciones de SDA/SCL.

-Es importante saber que cuando el maestro ya ha iniciado el modo TWI y ha mandado la address + R/W -los primeros 8 bits- con el simple hecho de cambiar en DDRB la direccion de la linea de output a input para obtener la lectura del ACK AUTOMATICAMENTE la linea SDA se va a HIGH, lo mismo en caso contrario cuando se retoma el control de SDA (osea cambiar de input a output mediante DDRB) AUTOMATICAMENTE la linea se va a LOW.

-Observando el diagrama de la comunicacion I2C es evidente que el ACK tiene como duracion solo un ciclo de reloj, entonces se hace necesario forzar el overflow del clock en el codigo manualmente en esos puntos despues de un pulso cuando solo se va a transmitir/leer el ACK.

-La señal de start, el overflow del timer (cada 8-bits) y la señal de stop cuentan con la opcion de generar una interrupcion, misma que debe ser habilitada si se desea utilizar. Se debe tomar en cuenta que cualquier instruccion que se programe dentro de la ISR tomara tiempo para su ejecucion y esto podria afectar el timming en algunas aplicaciones por lo que se aconseja activarlas solo cuando sea critico (esta es la razon por la cual no se puede generar una señal de clock adecuada utilizando el Timer/Counter 0 con una ISR para tooglear la linea de SCL y esto no se advierte en el datasheet).

I2C progamado en C sin bibliotecas

En los parrafos anteriores se contiene un resumen muy muy compacto de la I2C totalmente orientado a los Tiny, en base a eso hice el codigo de ambos modos de operacion separado en funciones para su mejor comprension:

  • I2C con ATtiny85 en modo master/maestro
//MASTER

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

//-------------Slave address + R/W bit y la data a ser enviada----------------
uint8_t data = 0b11001010; //Cualquier dato de 8-bits puede mandarse
uint8_t slave_write = 0x0A; //Seleccion del esclavo (dato arbitrario)
uint8_t slave_read = 0x0B; //Seleccion del esclavo (dato arbitrario)
//-----------------------------------------------------------------------

uint8_t usi_data;


void usi_init(void)
{
	DDRB |= 1<<PB2 | 1<<PB0; //Configuracion de SDA y SCL como salidas (tomar el control)
	PORTB |= 1<<PB2 | 1<<PB0; //Ambas en alto previo al inicio del I2C
	USICR |= 1<<USICS1 | 1<<USICLK; //Señal de clock externa, flanco positivo. Counter software strobe (USITC)
	//USICR |= 1<<USIOIE; //Counter overflow eneable
	//USICR |= 1<<USISIE; //Star condition interrupt eneable
	//sei();

	USICR |= 1<<USIWM1; //TWI eneable, sin mantener SCL en bajo tras el overflow del counter

	while(!(USISR &(1<<USISIF))); //Esperar a la señal de start
	_delay_us(20);
	PORTB &= ~(1<<PB2); //Baja SCL despues de la señal de start
	_delay_us(20);
	USISR |= 1<<USISIF; //Reset de la start condition interrupt flag
	USIDR = slave_write; //Posicionar en memoria los 7-bit de la address del esclavo + 1-bit R=0 W=1
}


void transmit_data(void)
{
	while(!(USISR &(1<<USIOIF))) //Repetir la señal del clock hasta que ocurra el overflow del clock (cada 8-bits)
	{
		USICR |= 1<<USITC; //Toogle clock pin
		_delay_us(5); //Tiempo en alto
		USICR |= 1<<USITC; //Toogle clock pin
		_delay_us(5); //Tiempo en bajo
	} 
	USISR |= 1<<USIOIF; //Limpiar la flag de overflow
}


void check_ack(void)
{
	DDRB &= ~(1<<PB0); //Cambia la direccion de SDA para recibir el bit ACK
	USISR |= 1<<USICNT3 | 1<<USICNT2 | 1<<USICNT1; //Prepara el contador para forzar el overflow cuando se reciba el ACK
	transmit_data();
	DDRB |= 1<<PB0; //Recobrar el control de la linea de datos SDA
	usi_data = USIDR; //ACK status
}


void usi_stop(void)
{
		PORTB &= ~(1<<PB0); //Preparacion para señal de stop
		_delay_us(10);
		PORTB |= 1<<PB2; //SCL en alto
		_delay_us(5); //Separacion entre cambio de estado de las lineas
		PORTB |= 1<<PB0; //SDA en alto

		USICR &= ~(1<<USIWM1); //TWI disable
		PORTB &= ~(1<<PB2) | ~(1<<PB0); //Salidas en bajo (lineas apagadas)
}


int main(void)
{
	DDRB |= 1<<PB3; //LED señalizador
	_delay_ms(800); //Tiempo de estabilizacion
	usi_init(); 
	transmit_data();
	check_ack();	

	if (!(usi_data & 0x01)) //Si hay match con alguna address
	{
		USIDR = data; //Data para enviarse despues de recibir el ACK
		transmit_data();
		check_ack();
		
		if (!(usi_data & 0x01)) //Si el esclavo envio un ACK
		{
			PORTB |= 1<<PB3; //LED encendido para señalizar el match
		}
		usi_stop();
	}

	else //Si ningun esclavo responde a la adress, parar la comunicacion I2C
	{
		usi_stop();
	}
}

  • I2C con ATtiny85 en modo slave/esclavo
//SLAVE

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

//--------Identidad del esclavo--------
uint8_t address_read = 0x0B;
uint8_t address_write = 0x0A;
//-------------------------------------

uint8_t usi_data;


void usi_init(void)
{ 
	USICR |= 1<<USICS1; //Fuente de clock externa, flanco positivo. Counter software strobe (USITC)
	//USICR |= 1<<USIOIE; //Counter overflow eneable
	//USICR |= 1<<USISIE; //Start condition interrupt eneable
	//sei();

	USICR |= 1<<USIWM1; //TWI eneable
}


void get_data(void)
{
	USISR &= 0xF0; //Limpiar todas las flags y counter bits = 0; La condicion de start tambien incrementa el contador
	while(!(USISR &(1<<USIOIF))); //Esperar a que el registro se llene
	USISR |= 1<<USIOIF; //Limpiar la flag de overflow
}


void usi_stop(void)
{
	USICR &= ~(1<<USIWM1); //TWI disable
}


void listen_address(void)
{
	while(!(USISR &(1<<USISIF))); //Espera a la señal de start
	get_data();
}


void ack_response(void)
{
	DDRB |= 1<<PB0; //Toma el control de SDA; Esto pone a la linea en LOW por default
	while((USISR & 0x02) == 0); //Esperar a que incremente el contador
	_delay_us(5); //Tiempo de estabilizacion
	DDRB &= ~(1<<PB0); //Libera la linea SDA
	USISR |= 1<<USIOIF; //Limpiar la flag de overflow
}


int main(void)
{
	_delay_ms(100); //Tiempo de estabilizacion
	usi_init();
	listen_address();
	usi_data = USIDR; //Recuperar la address del registro de datos
	if ((usi_data == address_write) || (usi_data == address_read)) //Checar si la address da un match
	{
		ack_response();
		get_data();
		usi_data = USIDR; //Recuperar la data

		if (usi_data == 0b11001010) ack_response(); //Podria mandarse el ACK sin hacer la evaluacion del if si se desea
	}
	usi_stop();
}

Ambos codigos estan diseñados para que interactuen entre ellos y ejecutan un ciclo completo de la comunicacion que incluye:

  • Generacion de la señal de start por parte del maestro
  • Clock por software (bit de strobo)
  • Validacion de address en el esclavo
  • ACK por parte del esclavo
  • Data de 8-bits
  • Validacion de la data recibida en el esclavo
  • Señal de stop por parte del maestro

Ambos codigo estan pensados para que se puedan hacer las pequeñas adaptaciones necesarias y satisfacer las particularidades de multiples proyectos.

Hasta aqui dejo esta pequeña aportacion de un tema que tiene muy poca informacion al respecto con el enfoque de no tener que usar arduinos o bibliotecas; el conocimiento los hara libres…

Published by Rvziel

(No uso acentos y estoy consciente de ello pero ese es mi cuento) Tengo muchas otras aficiones totalmente diferentes a lo que posteo; hagamos que el conocimiento sea libre y devolvamos el poder a la gente.

2 thoughts on “I2C ATtiny85 Maestro y Esclavo (AVR TWI – Two Wire Interface)

    1. La forma facil es mandar el bit de lectura desde el maestro hacia el esclavo y una vez que el esclavo responda con su ACK podra mandar la información en trenes de 8 bits que se guardarán y serán accesibles mediante la lectura del registro USIDR en el maestro

      Like

Leave a comment

Design a site like this with WordPress.com
Get started