I2C Slave. Приклад для AVR мікроконтролера Atmega


06.11.2014

I2C Шина (TWI)

I2C - послідовна шина даних для зв`язку інтегральних схем, що використовує дві двонаправлені лінії зв`язку (SDA і SCL). Використовується для з`єднання низькошвидкісних периферійних компонентів. Назва є абревіатурою слів Inter-Integrated Circuit. TWI (Two Wire Interface) або TWSI (Two Wire Serial Interface) по суті та ж сама шина I2C, але використовує іншу назву з ліцензійних причин. I2C використовує дві двонаправлені лінії, підтягнуті до напруги живлення і керовані через відкритий колектор або відкритий стік - послідовна лінія даних (SDA, англ. Serial DAta) і послідовна лінія тактування (SCL, англ. Serial CLock). Стандартні напруги живлення +5 В або +3,3 В.

I2C

Класична адресація включає 7-бітовий адресний простір з 16 зарезервованими адресами. Тобто 112 вільних адрес для підключення периферії на одну шину. Основний режим роботи - 100 Кбіт/с; 10 Кбіт/с в режимі роботи із зниженою швидкістю. Стандарт допускає призупинення тактування при роботі з повільними пристроями.

Після перегляду стандарту в 1992 році стало можливим підключення ще більшої кількості пристроїв на одну шину (за рахунок 10-бітної адресації), а також збільшилась швидкість до 400 Кбіт/с в швидкісному режимі. Відповідно, доступна кількість вільних вузлів зросла до 1008. Максимальна допустима кількість мікросхем, приєднаних до однієї шині, обмежується максимальною ємністю шини в 400 пФ. Версія стандарту 2.0 1998 року представила високошвидкісний режим роботи зі швидкістю до 3,4 Мбіт/с зі зниженим енергоспоживанням. Версія 2.1 2001 року внесла лише незначні доопрацювання.

I2C_data_transfer Послідовність передачі даних

Стан СТАРТ і СТОП

Процедура обміну починається з того, що Master формує стан СТАРТ: генерує перехід сигналу лінії SDA з ВИСОКОГО стану в НИЗЬКИЙ при ВИСОКОМУ рівні на лінії SCL. Цей перехід сприймається усіма пристроями, підключеними до шини, як ознака початку процедури обміну. Генерація синхросигналу - це завжди обов`язок Master-а; кожен Master генерує свій власний сигнал синхронізації при пересиланні даних по шині. Процедура обміну завершується тим, що Master формує стан СТОП - перехід стану лінії SDA з НИЗЬКОГО стану в ВИСОКЕ при ВИСОКОМУ стані лінії SCL. Стан СТАРТ і СТОП завжди генеруються Master-ом. Вважається, що шина зайнята після фіксації стану СТАРТ. Шина вважається звільненою через деякий час після фіксації стану СТОП. При передачі даних по шині I2C кожен Master генерує свій синхросигнал на лінії SCL. Після формування стану СТАРТ, Master опускає стан лінії SCL в низький стан і виставляє на лінію SDA старший біт першого байта повідомлення. Кількість байт в повідомленні не обмежена. Специфікація шини I2C дозволяє зміни на лінії SDA тільки при низькому рівні сигналу на лінії SCL. Дані дійсні і повинні залишатися стабільними тільки під час ВИСОКОГО стану синхроімпульса. Для підтвердження прийому байта від Master-передавача приймачем в специфікації протоколу обміну по шині I2C вводиться спеціальний біт підтвердження, який виставляється на шину SDA після прийому 8 біта даних.

Підтвердження

Таким чином передача 8 біт даних від передавача до приймача завершуються додатковим циклом (формуванням 9-го тактового імпульсу лінії SCL), при якому приймач виставляє низький рівень сигналу на лінії SDA, як ознака успішного прийому байта. Підтвердження при передачі даних обов`язкове, крім випадків закінчення передачі Slave стороною. Відповідний імпульс синхронізації генерується Master-ом. Передавач відпускає (ВИСОКУ) лінію SDA на час синхроімпульсу підтвердження. Приймач повинен утримувати лінію SDA протягом ВИСОКОГО стану синхроімпульсу підтвердження в стабільному НИЗЬКОМУ стані. В тому випадку, коли приймач не може підтвердити свою адресу (наприклад, коли він виконує в даний момент будь-які функції реального часу), лінія даних повинна бути залишена в ВИСОКОМУ стані. Після цього Master може видати сигнал СТОП для переривання пересилання даних. Якщо в пересиланні бере участь Master-приймач, то він повинен повідомити про закінчення передачі Slave-передавача шляхом непідтвердження останнього байта. Slave-передавач повинен звільнити лінію даних для того, щоб дозволити Master-у видати сигнал СТОП або повторити сигнал СТАРТ.

Синхронізація

Синхронізація виконується з використанням підключення до лінії SCL за правилом монтажного "І". Це означає, що Master не має монопольного права на управління переходом лінії SCL з НИЗЬКОГО стану в ВИСОКИЙ. В тому випадку, коли ведомому необхідний додатковий час на обробку прийнятого біта, він має можливість утримувати лінію SCL в низькому стані до моменту готовності до прийому наступного біта. Таким чином, лінія SCL перебуватиме в НИЗЬКОМУ стані протягом найдовшого НИЗЬКОЇ періоду синхросигналов. Пристрої з більш коротким НИЗКИМ періодом будуть входити в стан очікування на час, поки не скінчиться довгий період. Коли у всіх задіяних пристроїв скінчиться НИЗЬКИЙ період синхросигналу, лінія SCL перейде в ВИСОКИЙ стан. Всі пристрої почнуть проходити ВИСОКИЙ період своїх синхросигналов. Перший пристрій, у якого скінчиться цей період, знову встановить лінію SCL в низький стан. Таким чином, НИЗЬКИЙ період сінхролініі SCL визначається найдовшим періодом синхронізації з усіх задіяних пристроїв, а ВИСОКИЙ період визначається самим коротким періодом синхронізації пристроїв. Механізм синхронізації може бути використаний приймачами як засіб управління пересиланням даних на байтовому і битовом рівнях. На рівні байта, якщо пристрій може приймати байти даних з великою швидкістю, але вимагає певний час для збереження прийнятого байта або підготовки до прийому наступного, то воно може утримувати лінію SCL в НИЗЬКОМУ стані після прийому і підтвердження байта, переводячи таким чином передавач в стан очікування. На рівні бітів, пристрій, такий як мікроконтролер без вбудованих апаратних ліній I2C, може уповільнити частоту синхроімпульсів шляхом продовження їх НИЗЬКОГО періоду. Таким чином швидкість передачі будь-якого Master-а адаптується до швидкості повільного пристрою.

Адресація в шині I2C

Кожен пристрій, підключений до шини, може бути програмно адресовано за унікальною адресою. Для вибору приймача повідомлення Master-а використовує унікальну адресну компоненту в форматі посилки. При використанні однотипних пристроїв, інтегральні схеми часто мають додатковий селектор адреси, який може бути реалізований як у вигляді додаткових цифрових входів селектора адреси, так і у вигляді аналогового входу. При цьому адреси таких однотипних пристроїв виявляються рознесеними в адресному просторі пристроїв, підключених до шини. У звичайному режимі використовується 7-бітна адресація. Процедура адресації на шині I2C полягає в тому, що перший байт після сигналу СТАРТ визначає, який Slave адресується Master-ом для проведення циклу обміну. Виняток становить адреса «Спільного виклику», яка адресує всі пристрої на шині. Коли використовується ця адреса, всі пристрої в теорії повинні послати сигнал підтвердження. Однак, пристрої, які можуть обробляти «загальний виклик», на практиці зустрічаються рідко. Перші сім бітів першого байта утворюють адресу Slave-а. Восьмий, молодший біт, визначає напрямок пересилки даних. «Нуль» означає, що Master буде записувати інформацію в обраного Slave-а. «Одиниця» означає, що Master буде зчитувати інформацію зі Slave. Після того, як адреса відправлена, кожен пристрій в системі порівнює перші сім біт після сигналу СТАРТ зі своєю адресою. При збігу пристрій вважає себе обраним як Slave-приймач або як Slave-передавач, в залежності від біта напрямку. Адреса Slave може складатися з фіксованої і програмованої частини. Часто трапляється, що в системі буде кілька однотипних пристроїв (наприклад ІМС пам`яті, або драйверів світлодіодних індикаторів), тому за допомогою програмованої частини адреси стає можливим підключити до шини максимально можливу кількість таких пристроїв. Всі спеціалізовані ІМС, що підтримують роботу в стандарті шини I2C, мають набір фіксованих адрес, перелік яких зазначений виробником в описах контролерів. Комбінація біт 11110ХХ адреси зарезервована для 10-бітної адресації. Як випливає з специфікації шини, допускаються як прості формати обміну, так і комбіновані, коли в проміжку від стану СТАРТ до стану СТОП Master і Slave можуть виступати і як приймач, і як передавач даних. Комбіновані формати можуть бути використані, наприклад, для управління послідовної пам`яттю. Під час першого байта даних можна передавати адресу в пам`яті, який записується у внутрішній регістр-засувку. Після повторення сигналу старту і адреси Slave видаються дані з пам`яті. Всі рішення про авто-інкремент або декремент адреси, до якої стався попередній доступ, приймаються конструктором конкретного пристрою. Тому, в будь-якому випадку кращий спосіб уникнути неконтрольованої ситуації на шині перед використанням нової (або що раніше не використовуваної) ІМС слід ретельно вивчити її опис (datasheet або reference manual), отримавши його з сайту виробника.

Можливі проблеми

На сьогоднішній момент найбільш популярна швидкість використання I2C - 100 Кбіт/с та 400 Кбіт/с. Однак, треба розуміти, що використання на одній шині декількох пристроїв, які мають різні реалізації протоколу, та працюють на різних швідкостях може привести до непередбачених проблем. Найчастіше для забеспечення стабільної роботи доводиться обмежувати швидкість шини до швидкості самого повільного пристрою. Деякі пристрої, що працюють в режимі MASTER не підтримують призупинення тактування для повільних пристроїв (clock stretching). В результаті чого виникають помилки при роботі. В цьому випадку доводиться значно зменшувати швидкість шини або, якщо є така можливість, розносити швидкі і повільні пристрої по різним шинам.

Приклад реалізації I2C Slave на AVR мікроконтролері Atmega8


#include <avr/io.h>
#include <compat/twi.h>
#include <avr/interrupt.h>

#define I2CSLAVE_ADDR		0x4E

#define PORT_DDR			0xB0 // PORTB Settings
#define PORT_IN				0xB1 // Get PINB
#define PORT_OUT			0xB2 // Set PORTB

unsigned char regaddr; // Store the Requested Register Address
unsigned char regdata; // Store the Register Address Data

void i2c_slave_action(unsigned char rw_status)
{
	switch(regaddr) {
		// PORT
		case PORT_DDR:
			if (rw_status == 0)
				// read
				regdata = DDRB;
			else
				// write
				DDRB = regdata;
			break;

		case PORT_IN:
			if (rw_status == 0)
				// read
				regdata = PINB;
			break;

		case PORT_OUT:
			if (rw_status == 1)
				// write
				PORTB = regdata;
			break;
	}
}

ISR(TWI_vect)
{
	static unsigned char i2c_state;
	unsigned char twi_status;

	// Disable Global Interrupt
	cli();

	// Get TWI Status Register, mask the prescaler bits (TWPS1,TWPS0)
	twi_status=TWSR & 0xF8;     

	switch(twi_status) {
		case TW_SR_SLA_ACK:	// 0x60: SLA+W received, ACK returned
			i2c_state=0;	// Start I2C State for Register Address required
			break;

		case TW_SR_DATA_ACK:	// 0x80: data received, ACK returned
			if (i2c_state == 0) {
				regaddr = TWDR;	// Save data to the register address
				i2c_state = 1;
			} else {
				regdata = TWDR;	// Save to the register data
				i2c_state = 2;
			}
			break;

		case TW_SR_STOP:	// 0xA0: stop or repeated start condition received while selected
			if (i2c_state == 2) {
				i2c_slave_action(1);	// Call Write I2C Action (rw_status = 1)
				i2c_state = 0;		// Reset I2C State
			}
			break;

		case TW_ST_SLA_ACK:	// 0xA8: SLA+R received, ACK returned
		case TW_ST_DATA_ACK:	// 0xB8: data transmitted, ACK received
			if (i2c_state == 1) {
				i2c_slave_action(0);	// Call Read I2C Action (rw_status = 0)
				TWDR = regdata;		// Store data in TWDR register
				i2c_state = 0;		// Reset I2C State
			}
			break;

		case TW_ST_DATA_NACK:	// 0xC0: data transmitted, NACK received
		case TW_ST_LAST_DATA:	// 0xC8: last data byte transmitted, ACK received
		case TW_BUS_ERROR:	// 0x00: illegal start or stop condition
		default:
			i2c_state = 0;	// Back to the Begining State
	}
	// Clear TWINT Flag
	TWCR |= (1<<TWINT);
	// Enable Global Interrupt
	sei();
}

int main(void)
{
	// TWI Pull UP
	PORTC |= ((1<<PINC4) | (1<<PINC5));

	// Initial I2C Slave
	TWAR = I2CSLAVE_ADDR & 0xFE;	// Set I2C Address, Ignore I2C General Address 0x00
	TWDR = 0x00;			// Default Initial Value

	// Start Slave Listening: Clear TWINT Flag, Enable ACK, Enable TWI, TWI Interrupt Enable
	TWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN) | (1<<TWIE);

	// Enable Global Interrupt
	sei();

	// Initial Variable Used
	regaddr=0;
	regdata=0;

	while (1) {

	}

}

Тактова частота мікроконтролера має бути не меншою за 8 MHz.

У цьому прикладі, звертаючись по шині I2C до пристрою за адресою зазначеною в рядку:


#define I2CSLAVE_ADDR        0x4E

можна керувати портом B мікроконтролера, використовуючи адреси:

#define PORT_DDR    0xB0 // PORTB Settings #define PORT_IN        0xB1 // Get PINB #define PORT_OUT    0xB2 // Set PORTB

Для програмування порту на вхід, слід записати 0x00 за адресою 0xB0. Для отримання стану входів порту B потрібно прочитати один байт за адресою 0xB1. Для програмування порту на вихід записати 0xFF за адресою 0xB0. Щоб встановити всі виходи порту в 1 треба записати 0xFF за адресою 0xB2.

Використовуючи цей приклад можна створювати свої специфічні I2C Slave пристрої на базі AVR мікроконтролерів. Наприклад, модулі сенсорів, дисплеїв, тощо, інтерфейс яких потрібно уніфікувати.

Успіхів!

Корисно знати Схеми і прошивки
Коментарі:
Reboot_s говорить:
14.11.2014 17:26
Наідлінішій - найдовший, та синхроипульс - синхроімпульс. 
Я звик до ассемблеру, з СІ в мене поки стосунки не склались. Але в мене викликає запитання ініціалізація інтерфейсу TWI в основній частині програми. Назви бітів і регістру для всієї серії однакова? Ато Атмел має звичку в нових серіях залишати адресу регістру ту саму а назву на пару літер змінювати.

andre говорить:
15.11.2014 11:31
Дякую, помилки виправив. Щодо ініціалізації TWI,  і не тільки, краще заглядати у доку до контролера. Бо навіть у дуже схожих можуть бути розбіжності.

Додати коментар
Code
* - обов'язкові поля

Архіви