» » » Программная реализация протокола I2C на AVR в CodeVisionAVR

 
 
 

Программная реализация протокола I2C на AVR в CodeVisionAVR

Разместил Electronik83 27 августа 2016. Просмотров: 2171

9 Программная реализация протокола I2C на AVR в CodeVisionAVR Пару лет назад, изучая Atmega8, я захотел программно реализовать работу с устройствами на шине i2c, но как-то не задалось. С аппаратным i2c, напротив, не было никаких проблем и затея как-то забылась.

Но недавно я прочитал статью на Датагоре «Несколько функций для програмной реализации протокола I2C на AVR», в которой автор выложил свой пример программной реализации протокола под IAR. Я в тот же миг переписал всё в CodeVision, но, к сожалению, у меня опять не заработало как надо.
Решил набраться терпения и разобраться сам.

Разбираемся с протоколом i2c

Любой протокол можно разобрать по кирпичикам. Так вот, в данном протоколе таким «кирпичиком» является передача/приём одного бита. Схематично передачу одного бита я изобразил на рисунке.
Программная реализация протокола I2C на AVR в CodeVisionAVR


На этом рисунке я изобразил поведение линии SCL при передаче одного бита и длиной в один такт, а также пояснил некоторые понятия. С SCL вроде бы всё ясно, надо разобраться с SDA.
Так вот, при записи бита, надо на линии SDA выставлять значение, в соответствии с содержанием этого бита. Причём делать это лучше всего в середине паузы. Сразу появился вот такой код:
// пишем бит в шину
void i2c_wr_bit(char b) {
delay_us(i2c_time/2); // формируем первую половину паузы
if (b) wrsda; else rdsda;  // пишем бит...    
delay_us(i2c_time/2); // завершаем паузу
rdscl;                // выдаем фронт в линию SCL     
delay_us(i2c_time);   // формируем сам импульс
wrscl;                // выдаем спад в линию SCL
}


Далее рассмотрим чтение бита из шины. Нужно точно также протактировать линию SCL и прочитать значение линии SDA сразу после фронта импульса SCL. Вот код:
// читаем бит из шины
char i2c_rd_bit() {
char ret=1;          // временая переменная
rdsda;               // SDA - на чтение
delay_us(i2c_time);  // формируем паузу между импульсами  
rdscl;               // выдаем фронт SCL    
if (i2c_read) ret=0; // читаем SDA                                 
delay_us(i2c_time);  // формируем фронт
wrscl;               // формируем спад SCL
return ret;          // вернули, что прочитали...
}


Теперь пора рассмотреть все дефайны, которых, как известно, мало не бывает:
#include <mega328p.h>  // нужно для Атмегушки
#include <delay.h>     // нужно для задержек
// задержка в микросекундах (2 задержки на такт передачи)  
#define i2c_time 10 
//i2c порт
#define sdaprt PORTB.0 // порт для SDA         
#define sdaddr DDRB.0  // направлене для SDA 
#define sdapin PINB.0  // для чтения SDA
#define sclprt PORTB.1 // порт для SCL
#define sclddr DDRB.1  // направление для SCL                           
// управление битами шины
#define clrsda sdaprt=0; // переводим в 0 линию SDA
//#define setsda sdaprt=1; // оставил на всякий случай
#define clrscl sclprt=0; // переводим в 0 линию SCL 
//#define setscl sclprt=1; // оставил на всякий случай
#define rdscl  sclddr=0; // переводим SCL линию на вход
#define wrscl  sclddr=1; // переводим SCL линию на выход
#define rdsda  sdaddr=0; // переводим SDA линию на вход
#define wrsda  sdaddr=1; // переводим SDA линию на выход 
#define i2c_read sdapin  // для чтения SDA


Далее нужно научиться передавать байт. Тут ничего сложного:
// передача байта на шину
char i2c_wr(char data)  {
  //Функция возвращает 1, если есть ответ ACK
  char i; // счетчик для цикла. Если объявлять глобально, могут быть глюки
  for (i=0; i<8; i++) {         // выдаем в шину 8 бит
    i2c_wr_bit((data&0x80)==0); // отдаем бит
    data<<=1;  }                // двигаем байт
  // читаем «акнолидж» (подтверждение приёма)
  return i2c_rd_bit();          // вернули, что прочитали  
}


Хотелось бы пояснить, что шина i2c устроена так, что при передаче байта мы должны отправить восемь бит данных и потом прочитать бит — так называемый «акнолидж» (ack). Это такой бит подтверждения, посылаемый устройством на шине, который сигнализирует нам, что устройство благополучно «проглотило» наш байт.
При работе с устройствами этот бит, как правило, говорит устройству о конце/продолжении передачи при чтении нескольких байт из него. Так же иногда этот «акнолидж» надо иногда передавать устройству, особенно при чтении данных с него.

С записью байта понятно, разберемся с чтением. Тут ситуация в точности наоборот — мы сначала читаем восемь бит байта, а потом выдаем этот «акнолидж».
Реализация приема байта:
// прием байта с шины
char i2c_rd(char a)  {
  //если ack=1, то выдается подтверждение ack в шину
  char i, data=0;                // счетчик и принимаемый байт
  for (i=0; i<8; i++) {          // цикл приема бит
    if (!i2c_rd_bit()) data++;   // читаем бит
    if(i!=7) data<<=1;  }        // двигаем байт
  i2c_wr_bit(a);  // выдаем «акнолидж» (подтверждение приема)
  return data;    // вернули, что прочли
}


Теперь пропишем пресловутые функции старт/стоп, как требует протокол:
// старт i2c
void i2c_go(void)  {
  delay_us(i2c_time);  // ждем, пока предидущие сигналы устаканятся
  rdsda; rdscl;        // отпускаем шину
  delay_us(i2c_time);  // формируем паузу:)
  wrsda;  clrsda;      // давим SDA и ставим 0
  delay_us(i2c_time);  // задержка...
  wrscl;  clrscl;      // давим SCL и ставим 0
  delay_us(i2c_time);  // задержка...
}
// стоп i2c
void i2c_end(void)  {
  wrsda;               // давим SDA
  delay_us(i2c_time);  // ждем..
  rdscl;               // давим SCL
  delay_us(i2c_time);  // ждем..
  rdsda;               // отдаем SDA
}


Проверим код с помощью записи/считывания AT24C08

Для проверки я решил использовать Ардуино Nano и микросхему памяти AT24C08. Обратите внимание, аналоги, например чип ST24C08, имеют немного другую распиновку, сверяйтесь с даташитами.
Для контроля работы сначала хотел использовать последовательный порт, но потом решил, что это лишнее и решил обойтись обычным светодиодом, ведь он уже распаян на Ардуинке. Работу с микросхемой AT24C08 расписывать не буду, всё есть в документации к ней.

Идея проверки состоит в том, что мы сначала записываем в микросхему два байта кода, а потом читаем их и сравниваем с оригиналом. Если записанные и считанные байты совпадают, то мы зажигаем светодиод. Это и будет говорить о полной работоспособности протокола.


Теперь код:
// главная функция
void main(void)  {
DDRB.5=1;
delay_ms(100);   // ждем, пока питание устаканится
while (1)  {
char t=0;
delay_ms(100); // ждем немного
PORTB.5=0; // гасим тестовый светодиод
// пишем байт 0xF7 в память микросхемы по адресу 05
i2c_go();
i2c_wr(0xA0);  // i2c адрес микросхемы
i2c_wr(0x05);  // адрес в памяти
i2c_wr(0xF7);  // данные
i2c_end();
// пишем байт 0x3B в память микросхемы по адресу 06
i2c_go();
i2c_wr(0xA0);  // i2c адрес микросхемы
i2c_wr(0x06);  // адрес в памяти
i2c_wr(0x3B);  // данные
i2c_end();
// читаем память..
i2c_go();
i2c_wr(0xA0);  // i2c адрес микросхемы
i2c_wr(0x05);  // адрес для чтения
i2c_go();      // так надо........
i2c_wr(0xA1);  // запуск чтения
if (i2c_rd(1)==0xF7) t++; // принимаем данные с линии
if (i2c_rd(0)==0x3B) t++; // аск передаем 0, т.к. это последний байт для чтения
i2c_end();
// если все данные прочитаны верно
if (t==2) PORTB.5=1;  // включаем тестовый светодиод
}
}


Написанный код я сначала тестирую в Протеусе (ISIS 7 Proteus). Привожу осциллограмму работы:
Программная реализация протокола I2C на AVR в CodeVisionAVR


На осциллограмме видно, что синхросигнал линии SCL получен ровный, данные на линии SDA выставляются верно. Но есть и минус: всплески сигнала на линии SDA. Я долго выяснял, откуда они взялись. Оказалось, что этот всплеск возникает сразу после передачи/приёма бита «акнолидж» (ACK). Устранить этот недостаток мне так и не удалось, оставил, как есть. На работу данный всплеск не должен влиять.
Может на досуге ещё подумаю над этим или кто-нибудь из опытных читателей подскажет в комментариях.

Тестируем протокол в железе

Переходим к тесту в железе. Воткнул в беспаечную макетную плату (breadboard) платку ARDUINO NANO, микросхему AT24C08 и соединил всё проводками. Я для прошивки Ардуинки  использую стандартный Ардуиновский бутлоадер (загрузчик) и программу XLoader, а вы можете использовать привычный вам способ.

При первом запуске у меня пошел дым. Что-то не так! Оказалось, что я перепутал распиновку микросхемы памяти AT24C08. Обидно. В Ардуино у меня сгорел диод по питанию и микросхема памяти вышла из строя. Я заменил и то и другое.

Прошил, включаю: светодиод «молчит». Потыкал тестером и понял, что совершенно забыл про подтягивающие резисторы. Воткнул их. Молчок. Немного подумав и покурив даташит на AT24C08, подал на вывод памяти WP (Write Protect — защита записи, pin 7) не ноль, а единицу.
Программная реализация протокола I2C на AVR в CodeVisionAVR

И, вуаля, всё заработало:
Программная реализация протокола I2C на AVR в CodeVisionAVR


Итого

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

Всем удачи, а меня с почином. Это первая моя статья.
В скором времени планирую написать статью о протоколе SPI.

Об авторе

Павел Сумароков (Electronik83)
РФ, Северодвинск
Радиоэлектроника, программирование.
 

Понравилось? Палец вверх!

  • всего лайков: 32

Поделись с друзьями!


Связанные материалы:


Kaspersky FREE. Бесплатная годовая лицензия для России, Белоруссии и Украины

Привет, друзья! Очередной подарок от kaspersky.ru — антивирус «Kaspersky FREE». Спасибо...

Программная реализация протокола SPI на AVR в CodeVisionAVR

Всем коллегам и согражданам привет! Увлёкся я изучением протоколов. Про реализацию ...

Блок управления вытяжным вентилятором. Наладка, результаты

Всем здоровья! Утюг починен. Хочу коротко отчитаться в проделанной работе и результатах...

Пусть всегда будет солнце!

Сегодня День Победы. Мы так долго живём без войны, что начали забывать, что это...

17/01 киберсубботник на портале

Привет, дрУги! В ближайщую субботу на нашем портале и в магазине будет...

Несколько функций для програмной реализации протокола I2C на AVR

Добрый день, дорогие друзья! Решил поделиться с вами несколькими функция для работы...

Библиотеки Diptrace +3D. Разъёмы PBS (розетки)

Продолжая тему библиотек для Diptrace, хочу предложить розетки однорядные PBS для пайки...

Генератор v.2.0 с непрерывным режимом для проверки телефонных линий

Учитывая пожелания трудящихся, в том числе монтеров–связистов, в генератор для проверки телефонных...

Toshiba - это хорошо! Новейший каталог 4Q2012

Предлагаю вниманию сограждан новейший каталог Toshiba Bipolar Power Transistors — 4 квартал...

Transformer: программа расчета маломощного (до 500 Вт) силового трансформатора на

Предлагаю начинающим очень простую программулину для расчета силовых трансформаторов. Автор...

Датагорская Ярмарка электроники v.2 открылась!

Уважаемые граждане Датагории и гости нашего кибер-города! После обновления открылась...

Пишем комментарии правильно

Здравствуйте, уважаемые граждане Датагории! Сегодня я расскажу вам, зачем у нас есть...
<
  • Главный редактор
28 августа 2016 12:24

Игорь Петрович Котов / Datagor

  • Регистрация: 25.02.2011
  • Публикаций: 261
  • Комментариев: 1599
 
  • 0
Павел, действительно с почином! handshake
Подробно, понятно, с хорошей практической частью.

<
  • Бонус
28 августа 2016 12:38

Ербол / erbol

  • Регистрация: 11.12.2014
  • Публикаций: 7
  • Комментариев: 58
 
  • +1
Спасибо, Павел! handshake
Знания, которые приходят, когда "набираешься терпения и разбираешься сам" остаются если не навсегда, то очень надолго. Да и объяснения таких знаний всегда понятны для других, как в этой статье smile

<
  • Гражданин
28 августа 2016 16:11

Радик / galrad

  • Регистрация: 23.08.2011
  • Публикаций: 12
  • Комментариев: 84
 
  • 0
Хорошая статья, частично закрывающая пробел в освоении механизмов программирования микроконтроллеров. Самое главное - простота изложения материала, с возможностью практического применения! Для последовательной передачи данных нужно освоить 3 протокола - SPI, I2C (не путайте с I2S), UART (USART). Отдельным пунктом можно назвать USB и CAN, но это уже для особо продвинутых... smile
Самый быстрый (и самый легкий - протокол SPI, протокол I2C не самый быстрый но, более селективный, позволяющий работать одновременно нескольким устройствам, UART - это привычный COM порт).
Очень надеюсь, что Павел не обойдет стороной указанные протоколы последовательной передачи данных.
Павел, статья зачетная, Спасибо!

<
  • Главный редактор
28 августа 2016 22:53

Игорь Петрович Котов / Datagor

  • Регистрация: 25.02.2011
  • Публикаций: 261
  • Комментариев: 1599
 
  • 0
В работе вторая статья Павла, об SPI протоколе.

<
  • Бонус
29 августа 2016 01:58

Ербол / erbol

  • Регистрация: 11.12.2014
  • Публикаций: 7
  • Комментариев: 58
 
  • 0
Павел, Вы не поднимали апноут AVR156? Возможно, там найдётся подсказка по всплескам SDA

<
  • Подписчик
31 августа 2016 10:09

Игорь / StalKer-NightMan

  • Регистрация: 15.03.2012
  • Публикаций: 1
  • Комментариев: 84
 
  • 0
Павел, спасибо, хорошо изложен материал.
Ждем продолжения.

<
  • Кандидат
4 сентября 2016 04:10

Павел Сумароков / Electronik83

  • Регистрация: 29.08.2015
  • Публикаций: 2
  • Комментариев: 4
 
  • 0
Я не знаю, что такое апноут..

<
  • Главный редактор
4 сентября 2016 22:00

Игорь Петрович Котов / Datagor

  • Регистрация: 25.02.2011
  • Публикаций: 261
  • Комментариев: 1599
 
  • 0
Павел, апноут = appnote = application note. Типа, заметка о применении. Нырок в практику.
Выпускаются всеми производителями на равне с даташитами.

Ищется по фразе "appnote + номер"
Atmel AVR156: TWI Master Bit Bang Driver
//www.atmel.com/images/doc42010.pdf

<
  • Бонус
5 сентября 2016 00:24

Ербол / erbol

  • Регистрация: 11.12.2014
  • Публикаций: 7
  • Комментариев: 58
 
  • 0
Павел, к пояснениям Игоря могу добавить следующее. Если зайти на сайт Atmel и в поиске набрать интересующую тему (например, twi, uart, spi или любую другую), то получите список материалов, которыми компания располагает по теме - даташиты, апноуты (заметки) и пр. В этих апноутах достаточно подробно, с приложением кода на Си или ассемблере, излагается тот или иной вопрос. Очень полезная штука, на мой взгляд

Информация
Вы не можете участвовать в комментировании. Вероятные причины:
— Администратор остановил комментирование этой статьи.
— Вы не авторизовались на сайте. Войдите с паролем.
— Вы не зарегистрированы у нас. Зарегистрируйтесь.
— Вы зарегистрированы, но имеете низкий уровень доступа. Получите полный доступ.