Содержание
Урок 1. Первый проект
Урок 2. Управление кнопками
Урок 3. Подключение LCD
Урок 4. Использование ШИМ
Урок 5. Таймеры
Урок 6.1. Статическая индикация
Урок 6.2. Динамическая индикация
Урок 7.1. Генерация звука
Урок 7.2. Генерация звука. Продолжение
Урок 8.1. Передача данных через UART
Урок 8.2. Передача данных через UART. Продолжение»
Урок 9. Передача данных через SPI
Урок 10. Изучение АЦП. Простой вольтметр
Урок 11. Получение синуса при помощи ШИМ
Урок 12. Измерение температуры
Урок 13. Внешние прерывания.
Урок 14. Использование отладчика
Урок 15.1. Управление инкрементальным энкодером
Урок 15.2. Управление громкостью, при помощи энкодера
Урок 16. Управление RGB светодиодом
Урок 17. Использование ИК
Урок 18.1. Знакомство с графическим дисплеем
Урок 18.2 Вывод изображения на графический дисплей
Урок 18.3 Вывод русскоязычного текста
Урок 19. Формирование сигнала, при помощи ЦАП (R2R)
Урок 20. Опрос матричной клавиатуры
Урок 21. Сторожевой таймер
Урок 22.1 Воспроизведение wav. Введение.
Урок 22.2 Воспроизведение wav. Продолжение.
Урок 23.1 Работа с внешней памятью
Урок 23.2 Работа с файловой системой Fat

Статья про I2С должна была появиться уже давно. Не смотря на кажущуюся простоту работы интерфейса, охватить все оказалось довольно непросто, поэтому подходящей фазы луны так и не было чтобы собрать все в кучу 🙂 Ситуацию решил исправить iEugene0x7CA и внести свет науки в массы, я лишь поправил стиль изложения.

Вначале нужно познакомить читателя  с тем, о чем дальше пойдет речь. Для обмена данными с периферией существуют различные интерфейсы, например мы уже ранее рассматривали SPI. Его недостаток — для обмена данными используются четыре ножки, если задействовано несколько устройств, то ножку CS — chip select нужно тянуть для каждого устройства отдельную. Таким образом, если используются три внешних устройства, то у микроконтроллера будет задействовано шесть ножек.

Ножки это важный ресурс и высокая скорость, которой обладает SPI не всегда нужна, поэтому альтернативой является интерфейс I2C, который работает со скоростью до 400кГц и обладает 7 битным адресным пространством и теоретически позволяет подключать до 128 ведомых устройств. Ну и самое главное, для передачи данных используется всего две ножки микроконтроллера и не важно сколько у вас сидит устройств на шине данных 1, 2 или 128.
twi_1

Сами ножки называются 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.
stateyka_pic1
stateyka_pic2
Подробно рассмотрим 2 основных фичи, к остальному при желании функции написать будет не проблема. Основные в данном случае- это запись произвольного байта и чтение, тоже произвольного(byte write и random read). Адресация в 24LC512 не страничная, а побайтовая, работать с такой системой очень удобно. Проще всего записывать данные, смотрим сейчас одним глазом на диаграмму из даташита и пытаемся разобраться, что же там происходит:
1. Посылаем на шину команду «старт». На практике это просто подтягивание шины на 1 такт, после этого все подключенные к ней устройства начинают «слушать» следующий байт, указывающий адрес микросхемы, с которой мы хотим работать. Старт- отдельная функция их i2c.h либы;
2. Посылаем байт с адресом.
stateyka_pic

Как видим, первый бит адресного байта(читается справа-налево) указывает, будем мы работать с 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 интерфейсом и строить свои функции для игры с таковой!

105 комментариев: Урок 24.1 Знакомство с I2C

  • Здравствуйте !!!
    Необходимо подключить MLX90614 к Atmega16a. Он подключается через I2C канал, но у меня на порт C подключен LCD 16×2. Как мне быть?

  • Переносить ноги LCD

  • сдравствуйте, столкнулся с такой проблемкой, в даташите есть две строго определённые ноги SDA и SCL, а в код вижн авр при включении I2C предлагает выбрать на какие ноги кинуть линии SDA и SCL, как это понять?
    или в даташите подписанные SDA и SCL не для I2C, а для TWI?

  • либа cavr использует программный i2c. twi = i2c. если планируете использовать аппаратный i2c, то ноги будут жестко привязаны

  • Пытаюсь адаптировать данный код на С для PIC, пока не выходит, в компиляторе PCWHD нету #include как быть? Подскажите, тут, видимо не сложно, но я новичок.

  • опечатка читать #include

  • для PIC должны быть свои библиотеки

  • А какая частота обмена данными по I2C стандартная у ЦАВРа? 16МГц тактовая частота МК

  • 100кГц

  • А нельзя-ли сделать устройство которое считывает содержимое памяти одной eeprom и перекидывает её содержимое на другую?

  • можно

  • Нужно-ли создавать динамический массив если надо считать содержимое eeprom и вывести на терминал? И каким образом надо создавать динамический массив в CV AVR?

  • все зависит от того какой смысл вы вкладываете в слово динамический. читать можно побайтно, больше в уарт все равно не пролезет, да и дма в атмегах нет.

  • Здравствуйте!
    Вопрос 1: будет ли AVR I2C библиотека работать в ATtiny45?
    Вопрос 2: как назначить ATtiny45 ведомым?

  • Здравствуйте!
    У вас есть примеры работы по I2C c LCD ?
    Очень интересует вот такая схема Atmega -> I2C -> адаптер на PCF8574AT ->
    -> 1602 LCD на контроллере ST7066U

    Или м.б. есть пример сразу без адаптера Atmega -> 1602 LCD на контроллере ST7066U

  • нет, там где можно не использовать i2c — не использую, поэтому примеров с ним нет

  • Подскажите пожалуйста. Смотрите,( для примера у меня DS1307) когда мы пишем в приемник мы передаем адрес приемника, потом адрес слова и дальше оттуда читаем байт,все понятно. Ситуация как с вашей микросхемой.
    Когда мы читаем байт у вас тоже передается контрольный адрес, и затем также адреса слова и далее сами байты. когда все по 1 я понимаю.
    Но в дс1307 при чтении идет сразу <Data(n+X).
    Как мне прочитать например часы?какой это байт информации? если я не выбираю конкретно адрес( в вашем примере вы все равно выбираете) просто написано что данные начинаются с регистрового указателя передаваться.Как указать что мне нужен конкретный регистр часов например?

  • Вроде понял, чтобы внутренний счетчик остановить на нужном адресе для чтения нужно оборвать операцию записи, но зачем же тогда нужно просто схема чтения всех байтов полностью? как декоративная операция?? показать принцип???

  • ничего не понял. в i2c обычно то что я сталкивался посылаем адрес, затем значение, тоже самое читаем. Смотрите в даташите на свою микросхему нужные адреса.

  • Уважаемый админ CarTest задал интересный вопрос I2C -> адаптер на PCF8574AT ->
    -> 1602 LCD Вот очень интересная тема по ардуино многа инфы но CVAVR нету ничего ,вот у вас сайт по програмираванию и вы пользуетесь большой популярностью разве вам не интересно растолкавать модуль про каторого нету почти инфы

  • может быть, когда нибудь 😀

  • Здравствуйте, необходимо считать данные с TAS5404. Адрес м/сх задал как 0хD8. Нужно считать значения с субадреса 0x00 и 0x01. Задал функцию TDA_read () как у вас, только с одним адресом. В теле программы записываю чтение так: data_call= TDA_read (0x00), это чтобы считать из адреса 0х00. Но ничего не поступает. При этом запись проходит нормально. Что не так?

  • Если адрес задан верно, то обычно байтовый протокол начинается с записи адреса устройства к которому обращаешься, затем запись регистра, который надо прочитать, далее читаешь регистр. Конечно везде бывают исключения, все что могу посоветовать это посмотреть анализатором, что реально приходит от устройства.

  • А как поменять скорость передачи, работаю от внутреннего 8МГц?

  • Решил поменять с помощью изменения тактовой МК, но странно, что увеличив до 16МГц, мне CAVR написал, что SCL будет увеличен, до 460кГц, но по факту (смотрю осцилом), импульсы стали шире в два раза, попробовал в обратную сторону до 1МГц тактовая, а SCL стала быстрее.

  • а фьюзы то при этом меняли?

  • Нет, это вообще была бесполезная затея, внутренний RC максимальная 8МГц. А то что компилировал CAVR, это вообще глюки. Поставив версию 3.12, появилось нормально выбирать скорость софтового I2C. Там она ограничена 100кГц. В итоге разобрался, слейв не хотел нормально отвечать пока перед повторным start, не поставил stop. Спасибо вам за статью.

  • Доброго времени! Подскажите, плз, какое минимальное время задержки можно в принципе получить при считывании сигнала от акселерометра ADXL345 -> Mega328? Как минимизировать/оптимизировать это время? Стоит ли мастером назначить ADXL345, а Mega328 слейвом?

  • Судя по даташиту, на SPI скорость до 5МГц, а работать он может только как slave

  • Ни как не могу понять из приведенного исходно кода: какое значение присвоено переменной «high_byte» ?????

    Переменной low_byte условием цикла значение присваивается, message тоже проинициализированно, а со старшим байтом не понятно…..

  • глобальные переменные инициализируются нулем

  • Скажите пожалуйста как подключить i2c lcd на atmega8 в codevisionavr. Ест библиотека? Спасибо заранее.

  • у меня вопрос .Если использовать внешнюю EEPROM то мы во сколько раз превысим обьём памяти хекс

  • вопрос не понятен.

  • Вопрос про АСК, как его программно реализовать ?
    в cvavr — I2C реализован коряво , длительность очень затянута, поэтому использовать нереально ( надо мудрить с частотой микрухи в общем забросил это и всё пишу сам без штатных функций).
    Сам вопрос, я правильно понимаю после передачи бросаем линию и если там «0» то возвращаем линию и работаем дальше. т.е.
    #define CLK 1 // clk sck PB1
    #define DIO 2 // dio sda PB2
    PORTB=0x00;
    DDRB=0b00000000;
    **** передача байта***
    DDRB=0b00000100; //
    if ( PORTB.0==1 ) STOP // опять же через какое время он уронит в ноль, сколько ждать перед проверкой ?
    DDRB=0b00000000;

  • упс ошибка в условии
    * if ( PINB.2 == 1 ) STOP // так правильно

  • честно не знаю стоит ли рассказывать то, что в первоисточнике рассказано хорошо. есть замечательный документ в котором все расписано http://www.ti.com/lit/an/slva704/slva704.pdf

  • Подскажите где можно найти библиотеки для управления LCD по i2c под CAVR 3.12? Я не силён в программировании ещё, учусь.

  • Или как адаптировать с версии 3.32 под 3.12?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика