Статья про I2С должна была появиться уже давно. Не смотря на кажущуюся простоту работы интерфейса, охватить все оказалось довольно непросто, поэтому подходящей фазы луны так и не было чтобы собрать все в кучу 🙂 Ситуацию решил исправить iEugene0x7CA и внести свет науки в массы, я лишь поправил стиль изложения.
Вначале нужно познакомить читателя с тем, о чем дальше пойдет речь. Для обмена данными с периферией существуют различные интерфейсы, например мы уже ранее рассматривали SPI. Его недостаток — для обмена данными используются четыре ножки, если задействовано несколько устройств, то ножку CS — chip select нужно тянуть для каждого устройства отдельную. Таким образом, если используются три внешних устройства, то у микроконтроллера будет задействовано шесть ножек.
Ножки это важный ресурс и высокая скорость, которой обладает SPI не всегда нужна, поэтому альтернативой является интерфейс I2C, который работает со скоростью до 400кГц и обладает 7 битным адресным пространством и теоретически позволяет подключать до 128 ведомых устройств. Ну и самое главное, для передачи данных используется всего две ножки микроконтроллера и не важно сколько у вас сидит устройств на шине данных 1, 2 или 128.
Сами ножки называются SCL и SDA, по первой подаются тактовые импульсы, по второй передаются данные. Особенностью является то, что обе линии должны быть подтянуты к плюсу питания через резисторы, абсолютно всегда, иначе ничего работать не будет.
Теперь о самом главном, как микроконтроллер узнает с каким устройством он общается? Для этого у каждого устройства есть свой адрес. Адрес можно узнать в даташите на устройство. Кроме того, обычно несколько бит адреса микросхемы позволено выставлять пользователю, дабы тот мог повесить несколько микросхем одного типа на одну I2C шину, например если одной внешней 64 кБ EEPROM не хватило и разработчик решил повесить 8 таких. После отсылки адреса чипа на шину, если микросхема с таковым там была и все подключено верно — именно она будет воспринимать следующие байты, остальные информацию на шине проигнорируют.
В atmega8 есть два варианта передачи данных по протоколу I2c — использование аппаратного модуля TWI(two wire interface) или программного ногодрыга. Программная реализация достаточно проста в использовании, однако имеет недостаток — после отсылки данных микроконтроллером, нужно тупить и ждать пока внешнее устройство ответит, в случае с использования аппаратной шины прием можно организовать в прерывании. Однако, для микроконтроллеров не имеющих на своем борту аппаратного модуля это единственное решение. Кроме того, шину можно повесить на любой порт, что может быть удобно при разводке плат.
Для начального ознакомления мы будем использовать программную библиотеку, в Codevision вкладка визарда называется I2C, а библиотека i2c.h. Функций в библиотеке присутствует не так много: i2c_init() — необходима для инициализации; i2c_start() — инициализирует начало передачи данных, также возвращает 1, если шина свободна и 0, если шина занята; i2c_stop() — окончание передачи; i2c_read() — принять данные; i2c_write() — передать данные.
В функциях приема и передачи есть маленькая хитрость, называется ack — работает очень просто: после завершения приема/передачи 8-ми бит, следующий такт периферия сама подтягивает линию данных к земле, контроллер при этом только «слушает» линию, и если там действительно 0 (т.е. периферия удачно приняла/отдала байт и сигнализирует об этом мастеру), то процесс продолжается. Если нет, то случился какой-то сбой. Чтобы при чтении получать подтверждение приема, отправляем i2c_read(1), если не хотим i2c_read(0).
Одной теорией сыт не будешь, хорошо бы еще научиться конкретно опрашивать периферию! Какой она бывает? Компасы-магнитометры, гироскопы-акселерометры, термометры, датчики влажности, освещенности, атмосферного давления, внешние АЦП, внешнюю EEPROM, аудиопроцессоры, эквалайзеры, экранчики, ИК-дальномеры, и еще много всякого вкусного. В общем понятно думаю, что не умея работать с I2C — вы многое теряете.
Наконец обещанный экшн, протоколы связи из даташита на 24LC512.
Подробно рассмотрим 2 основных фичи, к остальному при желании функции написать будет не проблема. Основные в данном случае- это запись произвольного байта и чтение, тоже произвольного(byte write и random read). Адресация в 24LC512 не страничная, а побайтовая, работать с такой системой очень удобно. Проще всего записывать данные, смотрим сейчас одним глазом на диаграмму из даташита и пытаемся разобраться, что же там происходит:
1. Посылаем на шину команду «старт». На практике это просто подтягивание шины на 1 такт, после этого все подключенные к ней устройства начинают «слушать» следующий байт, указывающий адрес микросхемы, с которой мы хотим работать. Старт- отдельная функция их i2c.h либы;
2. Посылаем байт с адресом.
Как видим, первый бит адресного байта(читается справа-налево) указывает, будем мы работать с 24LC512 в режиме чтения или записи. Запись — это 0. Далее идут 3 адресных бита, которые может выставить пользователь, подтянув ножки 1, 2, и 3 нашей 24LC512 к земле или питанию. Для простоты подтягиваем все к земле. Нужно это, дабы можно было подключить на I2C шину не одну EEPROM этого типа. Следующие 4 бита- это уже заводской адерс конкретно этой микросхемы. Выходит, для обращения к микрухе в режиме записи нужно кинуть на шину число «0b10100000».
3. и 4. Теперь бросаем 2 байта с адерсом того байта, который мы хотим записать. 24LC512 имеет 64 кБ памяти и для адресации каждого из них нам нужно в аккурат 16-битное число(2^16=65536, что с поправкой на тот факт, что килобайт- это 1024 байта дает нам в аккурат 64 кБ).
5. Бросаем собственно то, что хотели записать.
6. Ну, и команда «стоп»!
Все, мы записали один байт в энергонезависимую память! Если посмотрите на функцию записи из исходника тестовой прошивки — вы найдете там эти 6 простых шагов, взятых из даташитной картинки. Аналогично диаграмме из даташита действуем и для чтения данных. По сути все так же, строго идем по даташиту, используя стандартные функции i2c.h. Единственный момент, могущий показаться не очень понятным- это строка «i2c_write(Ext_EEPROM_Adr | 1)», представляющая собой банально добавление к первому биту адреса единички, т.е. обращение к 24LC512 в режиме чтения.
#include <mega8.h> #include <delay.h> // I2C Bus functions #asm .equ __i2c_port=0x15 //Адрес порта C .equ __sda_bit=4 .equ __scl_bit=5 #endasm #include <i2c.h> #include <stdio.h> #define Ext_EEPROM_Adr 0b10100000 // Адрес 24LC512 на I2C шине unsigned char high_byte, low_byte, message[23]={'H','E','L','L','O',',',' ','e','x','t','e','r','n','a','l',' ','E','E','P','R','O','M','!'}; unsigned char eeprom_read(unsigned char address2, unsigned char address1) //Функция чтения из внешней EEPROM { unsigned char data; i2c_start(); //Кидаем команду "Cтарт" на шину I2C i2c_write(Ext_EEPROM_Adr); //Кидаем на шину адрес 24LC512 i2c_write(address2); //Старший байт адресного пространства 24LC512 i2c_write(address1); //Младший байт i2c_start(); //Снова посылаем "старт" в шину i2c_write(Ext_EEPROM_Adr | 1); //Обращаемся к 24LC512 в режиме чтения, т.е. по адресу 101000001 data=i2c_read(0); //Принимаем данные с шины и сохраняем в переменную i2c_stop(); //Посылаем команду "Cтоп" return data; //Возвращаем значение прочитанного } void eeprom_write(unsigned char address2, unsigned char address1, unsigned char data) //Функция записи во внешнюю EEPROM { i2c_start(); //Кидаем команду "Cтарт" на шину I2C i2c_write(Ext_EEPROM_Adr); //Кидаем на шину адрес 24LC512 i2c_write(address2); //Старший байт адресного пространства 24LC512 i2c_write(address1); //Младший байт i2c_write(data); //Посылаем байт для записи i2c_stop(); //Посылаем команду "Стоп" delay_ms(5); //Даем микросхеме время записать данные, EEPROM довольно медлительна } void main(void) { DDRD=0xFF; // I2C Bus initialization i2c_init(); // USART initialization // Communication Parameters: 8 Data, 1 Stop, No Parity // USART Receiver: Off // USART Transmitter: On // USART Mode: Asynchronous // USART Baud Rate: 9600 UCSRA=0x00; UCSRB=0x08; UCSRC=0x86; UBRRH=0x00; UBRRL=0x33; //Записываем для теста короткую строку "HELLO, external EEPROM!" в первые 23 байта внешней EEPROM for(low_byte=0; low_byte<23; low_byte++) { eeprom_write(high_byte, low_byte, message[low_byte]); } //Кидаем в UART содержимое первых 23-х байтов адресного пространства 24LC512 for(low_byte=0; low_byte<23; low_byte++) { putchar(eeprom_read(0x00,low_byte)); delay_ms(100); } while (1); } |
Поздравляшки! Вы только что прошли краткий курс работы с периферией на I2C шине, теперь можете покупать любую цацку с I2C интерфейсом и строить свои функции для игры с таковой!
Как поменять адрес микрухи при использовании i2c интерфейса? Напишите пример. Мне нужно использовать две однотипные микросхемы ad5933 с контроллером Atmega16
Думаю что никак. 100% гарантировать не могу, но адрес зашивается на заводе и менять его нельзя.
Можно ли одновременно использовать аппаратный и программный i2c в Atmega16?
вероятно можно, но надо пробовать
Это, конечно, занятно, но встроенная библиотека вешает контроллер на все время чтения/записи i2c, что не есть гут. Год назад занимался rtc ds1307 — в ней помимо данных времени есть еще куча места для хранения разных данных/настроек(всего 64 байта) и если их все сразу читать, то контроллер зависает на существенное время. Помогло прерывание i2c- дал задачу в 1 байт и ушел на основную программу. Но это было на ассемблере. Есть ли для CVAVR библиотека основанная на прерывании i2c?
Описанная в статье библиотека программная, у нее куча недостатков, но главный плюс — простота. Если ваш микроконтроллер поддерживает прерывания, то вы можете их использовать. У меня i2c не прижился, но вы можете посмотреть в мануале на CAVR там есть примеры, только называются не i2c, а twi.
Здравствуйте! Подскажите пожалуйста, можно ли реализовать передачу данных между несколькими устройствами (микроконтроллерами) по протоколу I2C посредством радиосвязи? Например имеется несколько устройств, управление которыми необходимо осуществлять на расстоянии (в основном это включение и выключение питания) с одного пульта… можно ли применить I2C в таком случае?
Спасибо!
Мне не доводилось видеть такие приемопередатчики. Думаю I2C не годится для этого. Лучше посмотрите в сторону nrf24l01
Помогите разобраться с внутренней eeprom atmega8. Как осуществляется чтение и запись eeprom в CodeVision?
Добавляете перед переменной ключевое слово eeprom float xz=1.5;
Так, это записали в eeprom 1.5. А как мне содержимое zx скопировать в float z ? Или содержимое float z записать в eeprom float zx?
Дальше работаете как с обычной переменной. float z = zx;
Ммм… в начале так и дел: eeprom float z=1.5; flash float x; x=z; затем результат x вывожу на lcd. На lcd показывает нули. А если z вывожу — показывает 1.5. Проверяю в proteus 7.8. Не понимаю в чем ошибка…
Возможно выводите не правильно
Ну я в функции вывода на lcd переменную x меняю на z и всё показывает 1.5 :-/ тип переменных одинаковый float… У вас нет простого примера работы с eeprom(чтение/запись)?
Попробуйте просто float x без flash
)) … попробую так. Но,по моему, так тоже не получалось))
кажется понял в чем проблема: я объявлял переменную (eeprom float z=1.5)-глобальной, до фукции main. И при компиляции code vision создавал фаил с расширением .eep А proteus при загрузке проекта не видит состояние eeprom… он только загружает .hex Я объявил eeprom float z — глобальной, инициализировал (z=1.5) в функции main и в proteus всё заработало. Но мне нужен файл с расширением .eep. как быть?
Используйте для отладки обычные переменные, суть программы от этого не поменяется
Так и сделаю. Благодарен за помощь!
«Как поменять адрес микрухи при использовании i2c интерфейса? Напишите пример. Мне нужно использовать две однотипные микросхемы ad5933 с контроллером Atmega16.»
Обычно есть ножки которые задают несколько младших бит адреса.
Возможно это не предусмотрено для этой микросхемы, по крайней мере в даташите я не увидел ничего подобного
Как реализовать вывод прочитанного байта на LCD 16×2 ❓ За ответ буду очень благодарен
Читаете байт по нужному адресу и выводите
Если надо 2 микрухи с одинаковым адресам управлять с Атмеги 16 — можно I2C организовать на 2 ножках программно(написать свою библиотеку) и еще на 2х родных PC0 и PC1 запустить TWI
Что же вы мой коммент не опубликовали про i2c мультиплексоры?
значит в спам попало
Мучает один вопрос, строки кода типа:
#asm
.equ __i2c_port=0x15 //Адрес порта C
.equ __sda_bit=4
.equ __scl_bit=5
#endasm
должен генерить CVAVR? они у меня почему то не появляются…
в последних версиях этот код не генерится
а как быть в таком случае? этот код не нужен? или его теперь писать ручками нужно?
если проект сгенерился без него, то не нужен
Как прочитанные с еепром данные вывести на экран ЖКИ? Дайте пожалуйста кусок кода
посмотрите 3 урок
Не компилируется код, пишет function argument #1 of type ‘const unsigned char’ is incompatible with required parameter of type ‘unsigned char’. Всякое пробовал, не вышло(
А не, всё заработало. Я вместо lcd_puts использовал lcd_putchar
Сделайте плиз статью про то, как общаться по I2C между двумя МК. Проект стоит
есть предпосылки к тому, чтобы сделать еще одну статью по i2c, более подробную, но пока она не в приоритете.
Собрал самое необходимое в библиотеку, i2ceeprom.h
Всё тоже самое что тут, только избавляет от громоздкого кода в основной программе
Ну и маленькая модификация от меня, в аргументах функции появился указатель на чип, тоесть, вначале делаем #define блабла 0b[адрес], а потом eeprom_write(блабла, …).
Делал для себя, надеюсь и вам пригодится 😉
http://rghost.ru/7qpqstMLb
Забыл добавить, пароль на скачивание — avrstart
Годится, только на будущее, в h файле не размещают функции, только их объявляют.