Avrstudio 4. библиотека для avr. модуль для i2c или twi

AvrStudio 4. Библиотека для AVR. Модуль для I2C или TWI

Введение

Для Arduino написано много библиотек бери да пользуйся, не исключение и шина  I2C. А если посмотреть в сторону тех кто пишет на AvrStudio, то будет голое поле. Библиотеку писал на основе открытой библиотеки которую я скачал с сайта http://www.procyonengineering.com.

Информации про интерфейс в интернете много, а библиотек для работы с ним нет. Только GitHub в помощь. 

Несколько слов про интерфейс. Интерфейс, по-философии, чем-то похож на интерфейс 1-wire. Только для 1-wire синхронизация идет по времени и сигналам, а тут только по отдельной линии. Привожу таблицу сравнения

I2C 1-Wire
сброс линии нет есть
стартовый бит есть есть
адрес устройства есть есть
Сигнал присутствия есть есть
стоповый бит есть нет

Лично для меня реализация интерфейса показалась достаточно сложной, объемной. Плюс данного интерфейса высокая скорость и возможность коммуникации между многими устройствами, что является не маловажным. Своего рода TCP/IP для мира микроконтроллеров. Можете посмотреть ради интереса фолианты по TCP/IP.

Подключение библиотеки

Перейдем к цели, а именно рассказать про использование библиотеки. Библиотека состоит из 2 файлов: i2c.h и i2c.c.

1. В начале необходимо скинуть эти 2 файла в папку с проектом.

2. Подключить в главном си-файле заголовочный файл: #include<\p>
#include
#include
int main(void)
{
while(1)
{
}
}

3. Затем необходимо открыть опции конфигурирования проекта: Project–>Configuration Option

3.1. В вкладке General необходимо в строке Frequency указать частоту тактирования контроллера . ВНИМАНИЕ: если это не сделать проект будет выдавать ошибку.

3.2.  Затем необходимо Включить корневую папку проекта в поиск Стандартных библиотек. Для этого во вкладке Include Directories нажимают кнопку New(Insert) и выбирают папку проекта.

3.3. Последний этап это включение файла i2c.c в проект. Для этого в дереве проекта на папке Source File кликают 2 раза ПКМ и выбирают пункт Add Existing File(s) и добавляют файл i2c.c. После этого проект должен собраться.

Библиотека может работать в режиме Мастер или Slave. В библиотеке можно подключить/отключить любой режим.

По-умолчанию библиотека собирается в режиме Master.

Описание концепции работы библиотеки

Иногда скачаешь библиотеку, и думаешь как ей пользоваться? А потом решаешь свою написать. Поэтому опишу почему пришел к такой структуре библиотеки. Библиотеку начал писать для работы с LCD2004 по I2C.

 Разных чипов работающих по I2C навалом, но все они работают по принципу EEPROM памяти. В начале Мастер посылает пакет в котором записывает в один или два байта адрес ячейки с которой хочет начать чтение данных в памяти, а затем Начинает их читать.

Интерфейс я бы сказал ОЧЕНЬ ВЕЖЛИВЫЙ, это тебе не UART. Хочу передаю, хочу не передаю и ответственности за приемник никакой. Тут же Мастер интересуется кому посылает, готов сам откликнуться на чужую нужду и бросить свое дело.

Ведомый же всегда скажет если дома, если есть место честно об этом скажет, а если не может больше принять данных, то тоже сообщит.

Рассмотрим запись и чтение данных из EEPROM памяти at24c512. Схема подключения представлена на рисунке.

В данном случае at24c512 имеет адрес устройства 0b1010000. Дебаггер для I2C присоединил для удобства. В таблице привожу общение контроллера с памятью. Само общение можно посмотреть с помощью Дебаггера. Каждая строка в таблице коммуникации это попадание в прерывание для Master.

Вот так выглядит таблица ячеек памяти для at24c512 после записи.

Номер ячейки памяти ЗначениеЯчейки памяти
Нет адреса Старший байт адрес (0x00)
Нет адреса Младший байт адрес (0x03)
0x0000 0xFF
0x0001 0xF0
0x0002 0xF1
0x0003 0xFF
0xXXYY 0xZZ
———————– ———————–
0xFFFE 0xFF
0xFFFF 0xFF

Перед нами стоит следующая задача. Записать два байта 0xF0 и 0xF1 в ячейки памяти с индексами 0x0001 и 0x0002 соответственно.

Для этого мы в начале отправляем адрес устройства с командой записи, а затем посылаем данные.

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

А ниже я привожу таблицу обмена между контроллером и памятью at24c512

Посылка Байт+(N)Ask Номер прерывания при выполнении для Master Действие Master Описание события Master после наступления прерывания Действие Slave Описание действия Slave
START-бит 0x08 SМастер посылает  СТАРТ-бит Мастер инициировал сеанс связи Нет действий Логика Slave готовиться к принятию адреса и сравнению со своим
0b1010000W_Ask 0x18 0b10100000Мастер отправил адрес Slave для записи Мастер отправил адрес Slave и получил Ask Ask Логика Slave приняла свой адрес для записи и отправила готовность к записи
0x00_Ask 0x28 0x00Мастер отправил старший адрес 0x00 ячейки памяти для at24c512. Мастер отправил данные и получил Ask, т.е. готовность к дальнейшему общению Ask Slave записал старший адрес ячейки памяти и готов дальнейшему общению
0x01_Ask 0x28 0x01Мастер отправил младший адрес 0x01 ячейки памяти для at24c512. Мастер отправил данные и получил Ask, т.е. готовность к дальнейшему общению Ask Slave записал младший адрес ячейки памяти и готов дальнейшему общению. Теперь следующий байт будет записан в ячейку с номером 0x0001
0xF0_Ask 0x28 0xF0Мастер отправил данные для Slave Мастер отправил данные и получил подтверждение для дальнейшей передачи Ask Slave записал данные (0xF0) в ячейку памяти с номером 0x0001. Номер адреса ячейки памяти увеличен на 1.
0xF1_Ask 0x28 0xF1Мастер отправил данные для Slave Мастер отправил данные и получил подтверждение для дальнейшей передачи Ask Slave записал данные (0xF1) в ячейку памяти с номером 0x0002. Номер адреса ячейки памяти увеличен на 1.
Stop Нет прерывания StopМастер закончил отправку данных и посылает Стоп-бит После отправки СТОП Мастер не попадает в прерывание Нет действия Slave прекращает сеанс общения

Следующая задача обратная. Считать значения ячеек памяти по адресам 0x0001 и 0x0002.

В начале Мастер посылает адрес Slave памяти с битом ЗАПИСИ, а затем 2 байта для записи адреса ячейки памяти Slave. После этого посылаем Повторный СТАРТ и снова Адрес Slave c битом ЧТЕНИЯ. И начинаем читать данные. Если мы прочитал последний байт, то Мастер не должен отправлять ASK. После этого Мастер посылаем СТОП. Сеанс общение закончился.

Посылка Байт+(N)Ask Номер прерывания при выполнении для Master Действие Master Описание события Master после наступления прерывания Действие Slave Описание действия Slave
START-бит 0x08 SМастер посылает  СТАРТ-бит Мастер инициировал сеанс связи Нет действий Логика Slave готовиться к принятию адреса и сравнению со своим
0b1010000W_Ask 0x18 0b10100000Мастер отправил адрес Slave для записи Мастер отправил адрес Slave и получил Ask Ask Логика Slave приняла свой адрес для записи и отправила готовность к записи
0x00_Ask 0x28 0x00Мастер отправил старший адрес 0x00 ячейки памяти для at24c512. Мастер отправил данные и получил Ask, т.е. готовность к дальнейшему общению Ask Slave записал старший адрес ячейки памяти и готов дальнейшему общению
0x01_Ask 0x28 0x01Мастер отправил младший адрес 0x01 ячейки памяти для at24c512. Мастер отправил данные и получил Ask, т.е. готовность к дальнейшему общению Ask Slave записал младший адрес ячейки памяти и готов дальнейшему общению. Теперь следующий байт будет записан в ячейку с номером 0x0001
ReSTART-бит 0x10 SМастер посылает  СТАРТ-бит Мастер инициировал сеанс связи Нет действий Логика Slave готовиться к принятию адреса и сравнению со своим
0b1010000R_Ask 0x40 0b1010000RМастер отправил адрес Slave c битом ЧТЕНИЯ Мастер получил подтверждения готовности Slave к чтению Ask Slave отправил готовность чтению. Передачи данных начинается с загруженного адреса (0x0001)
0xF0_Ask 0x50 Ask Мастер отправил подтверждение, т.е запрос на чтение следующего байта 0xF0 Slave отправил данные из ячейки 0x0001, увеличил счетчик на 1, принял запрос на передачу следующего байта
0xF1_NAsk 0x58 NAsk Мастер НЕ отправил ПОДТверждение, т.е запрос Мастер прочитал все необходимые байты. 0xF1 Slave отправил данные из ячейки 0x0002, увеличил счетчик на 1. Slave не принял подтверждения. Ожидает СТОП-бит.
Stop Нет прерывания StopМастер закончил отправку данных и посылает Стоп-бит После отправки СТОП Мастер не попадает в прерывание Нет действия Slave прекращает сеанс общения

Теория программирования

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

Инициализация, естественно, библиотеки.

void i2cInit(void);

Загрузка данных для отправки в виртуальный Мастер буфер

 uint8_t i2cMasterUploadBuf(uint8_t data);

Мы последовательно записываем данные в буфер, пока место не кончиться. Если байт записался, то выдаем нам 1, иначе 0. В это случае байты не записываются.

https://www.youtube.com/watch?v=jIY4deLSS_c

А если мы записала байты, а потом поняли что не то записал, что делать? Для этого есть функция сброса данных о загруженных байтах. Т.е. байт загруженные остались, а библиотека думает что передавать нечего.

void i2cMasterBufReset(void);

Данные загрузили, теперь надо отправить их адресату

uint8_t i2cMasterSendBuf(uint8_t deviceAdd);

А если надо данные принять, то в начале загружаем 2 байта адреса ячеек памяти в буфер и вызываем функцию передачи по адресу устройства в количество N – байт.

uint8_t i2cMasterReceive(uint8_t deviceAdd, uint8_t lenght);

Данные считали, теперь надо их получить для этого есть спец функция для считывания значений из Мастер буфера по индексам. Если данная ячейка существует, то результат функции будет 1, иначе 0.

uint8_t i2cMasterDownloadBufIndex(uint8_t* result, uint8_t index);

Максимальный объем памяти Мастер буфера 255 байт.

Практическое задание

Давайте реализуем такое задание. У нас будет 2 кнопки. Задача одной из них записывать последовательно 2 байта в EEPROM память at24c512 в ячейку 0х0001 при нажатии начиная с 0x00, 0x01, затем 0x02, 0x03 и т.д.  

Задача 2 кнопки это считывание значений и вывод их на на светодиоды 2 портов.

Вот схема из протеуса

Листинг программы с комментариями

//=====================================================

#include
#include void init(void)
{
DDRA=0xFF; // настроили на вывод
DDRB=0xFF; // настроили на вывод
PORTA=0; // Подтянули к земле PORTB=0; // Подтянули к земле
DDRD=0x00; // настроили на вход
PORTD=0xFF;// подтянули к питанию
}
#define ADDRES_at24c512 0b1010000
static volatile uint8_t temp=0x00,a[2];
int main(void)
{
init();
i2cInit(); // инициализация шины
while(1)
{ if ((PIND&0b1000)==0) { // Ожидаем когда отпустят кнопку while(!(PIND&0b1000)); // Загружаем старший адрес ячейки памяти i2cMasterUploadBuf(0x00); // Загружаем старший адрес ячейки памяти i2cMasterUploadBuf(0x01); // Загружаем данные для записи for(uint8_t i=0;i

Источник: http://cxem.net/mc/mc420.php

Аппаратный I2C (TWI) в микроконтроллерах AVR

При наличии на борту AVR аппаратной реализации I2C почему-то многие предпочитают программные реализации. Хотя, на мой скромный взгляд — использование железного варианта проще, стабильнее и удобнее.

Применение встроенного интерфейса и подразумевает работу на прерываниях, но сегодня мы обойдемся без оных. Для понимания работы контроллера это несколько проще, а переложить код на использование прерываний не составит труда.

Описывать шину I2C не имеет смысла, исчерпывающие описание можно найти на википедии, Казусе и конечно у DI HALT’a.

Последняя ссылка заслуживает особого внимания, там основное внимание уделяется именно AVR, но в качестве примеров используется RTOS, что несколько абстрагирует от последовательности работы.

Именно для того, чтобы дополнить статью DI HALT’a (а так же, чтобы не забыть что и как самому) и была написана эта небольшая заметка.

Итака, шина I2C глазами микроконтроллера AVR как всегда представляет собой несколько регистров, а именно:

  • TWBR — TWI Bit Rate Register: В этом регистре настраивается частота (скорость) шины, так же на частоту влияет биты TWPS0..1 в регистре TWSR
  • TWCR — TWI Control Register: Через этот регистр происходит все управление шиной
  • TWSR — TWI Status Register: За исключением первых трех бит (TWPS0..1 и зарезервированного) — регистр отражает состояние шины
  • TWDR — TWI Data Register: Как не сложно догадаться — регистр данных. Именно из него данные уходят по шине, и именно в него контроллер помещает полученные байты.

Частота шины рассчитывается по формуле: FSCL = FCPU/(16+2(TWBR)*4TWPS). И это единственное, что нужно сделать при инициализации.

Вся работа TWI сводится к алгоритму:

  1. Записать значение в регистр TWCR (а при передачи данных предварительно поместить байтик в TWDR)
  2. Дождаться флага TWINT в том же регистре TWCR (при работе с прерываниями — этот флаг вызовет прерывание по-вектору TWI)
  3. Получить статус из регистра TWSR — в зависимости от статуса, что-то делать или не делать дальше.

К этой не хитрой последовательности сводится вся логика работы шины, формирование стартов-рестартов-стопов, передача байта, прием байта и формирование ответа ACK.

Для простоты понимания, что и когда надо записывать в TWCR оформим небольшую функцию:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define TWI_START 0
#define TWI_RESTART 1
#define TWI_STOP 2
#define TWI_TRANSMIT 3
#define TWI_RECEIVE_ACK 4
#define TWI_RECEIVE_NACK 5
 
uint8_t twi(uint8_t action){ switch(action){ case TWI_START: case TWI_RESTART: TWCR = _BV(TWSTA) | _BV(TWEN) | _BV(TWINT);// Если нужно прерывание | _BV(TWIE); break; case TWI_STOP: TWCR = _BV(TWSTO) | _BV(TWEN) | _BV(TWINT);// | _BV(TWIE); break; case TWI_TRANSMIT: TWCR = _BV(TWEN) | _BV(TWINT);// | _BV(TWIE); break; case TWI_RECEIVE_ACK: TWCR = _BV(TWEN) | _BV(TWINT) | _BV(TWEA);//| _BV(TWIE); break; case TWI_RECEIVE_NACK: TWCR = _BV(TWEN) | _BV(TWINT);// | _BV(TWIE); break; } if(action != TWI_STOP)while (!(TWCR & _BV(TWINT))); return (TWSR & 0xF8);
}

#define TWI_START 0 #define TWI_RESTART 1 #define TWI_STOP 2 #define TWI_TRANSMIT 3 #define TWI_RECEIVE_ACK 4 #define TWI_RECEIVE_NACK 5 uint8_t twi(uint8_t action){ switch(action){ case TWI_START: case TWI_RESTART: TWCR = _BV(TWSTA) | _BV(TWEN) | _BV(TWINT);// Если нужно прерывание | _BV(TWIE); break; case TWI_STOP: TWCR = _BV(TWSTO) | _BV(TWEN) | _BV(TWINT);// | _BV(TWIE); break; case TWI_TRANSMIT: TWCR = _BV(TWEN) | _BV(TWINT);// | _BV(TWIE); break; case TWI_RECEIVE_ACK: TWCR = _BV(TWEN) | _BV(TWINT) | _BV(TWEA);//| _BV(TWIE); break; case TWI_RECEIVE_NACK: TWCR = _BV(TWEN) | _BV(TWINT);// | _BV(TWIE); break; } if(action != TWI_STOP)while (!(TWCR & _BV(TWINT))); return (TWSR & 0xF8); }

Функция получает требуемое действие в качестве аргумента, дожидается его выполнения и возвращает результат выполнения. Коды возврата довольно непонятно описаны в Datasheet, и очень хорошо у DI HALT’a (ссылка выше по тексту). Данные для передачи необходимо заранее загрузить в регистр TWDR перед выполнением

twi(TW_TRANSMIT)

После успешного получения байта с помощью

twi(TW_RECEIVE_ACK)

или

twi(TW_RECEIVE_NACK)

так же следует напрямую прочитать из TWDR
Пример чтения (без всяких проверок и контроля выполнения) 128 байтов из EEPROM 24C01S:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TWSR &= ~(_BV(TWPS0)|_BV(TWPS1)); // биты предделителя
TWBR=114; //Настраиваем частоту шины //(при 8MHz F_CPU получаем 8000000/(2*(16+114)) = 32kHz)
uint8_t arr[128]; twi(TWI_START); //формируем сигнал START
TWDR=0xA0; //Загружаем адрес EEPROM+W
twi(TWI_TRANSMIT); //Передаем адрес
TWDR=0x00; //Загружаем адрес байта
twi(TWI_TRANSMIT); //Передаем
twi(TWI_RESTART); //Формируем рестарт (RESTART)
TWDR=0xA1; //Загружаем адрес EEPROM+R
twi(TWI_TRANSMIT); //Передаем
uint8_t i; //Принимаем 127 байт из EEPROM, формируя после каждого ответ ACK
for(i=0;i

Источник: https://bevice.ru/posts/716.html

TWI ( I2C ) — режим Multi-Master. Библиотека ProcyonAVRlib. Шаг №34

Всем привет.

Закончив разбирать, в прошлой статье, использование ацп для кнопок коррекции времени, в этой статье продолжим разбирать вторую часть протокола TWI ( I²C ), а именно вопрос синхронизации и приоритета в случае нескольких ведущих на шине, также рассмотрим готовые библиотеки и разберем функции ведомого и ведущего режимов.

Рассмотрим что такое статусные коды. Немного напомню. Первая часть протокола была рассмотрена в статье №28, именно та информация, которая необходима для подключения схемы: один ведущий остальные ведомые. И рассмотрели пример подключения часов DS1307 в статье №29.

Немного хочется добавить, что несмотря на простоту протокола, общение контроллера по I²C (TWI) затруднено из-за изобилия возможных нештатных ситуаций на шине. По этой причине большинство систем используют I²C c единственным ведущим (Master) устройством и распространённые драйверы поддерживают только монопольный режим обмена по I²C.

В нашем разрабатываемом устройстве также предполагается один ведущий, но в качестве ведомого предполагается еще один микроконтроллер ATmega, который будет как принимать информацию, так ее и передавать обратно.

Для закрепления информации мы рассмотрим два ведущих на линии, взаимодействие прикладной программы с модулем TWI и статусные коды.

Экспериментировать будем с двумя Atmega 8 и часами реального времени DS1307 по следующей схеме.

https://www.youtube.com/watch?v=6RRhZ91gkmE

Используя данный интерфейс с двумя ведущими устройствами (режим Multi Master ), нам необходимо рассмотреть вопрос синхронизации частоты на общей шине, который возникает при несовпадении тактовых частот при нескольких ведущих.

Если посмотрите рисунок ниже и прикинете таблицу истинности, то видно что общий такт возникает только в момент высокого состояния на линии всех устройств. На рисунке, также видно случаи запаздывания и опережения тактов и результат за счет схемного подключения «МОНТАЖНОЕ И».

Второй вопрос, это вопрос приоритета, который возникает в случае когда два и более ведущих пытаются начать передачу данных. Понятно что для целостности передачи данных, на шине должен быть только один ведущий.

В данном случае в контроллере работает процесс распределения приоритетов (АРБИТРАЖ), который заключается в том что все ведущие устройства после выдачи данных на SDA, контролируют состояние на общей линии.

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

Также может возникнуть ситуация одновременного обращения двух и более ведущих, например к часам, то понятно что передаваемая информация должна совпадать. В данном случае прописаны три пункта в которых не должен происходить процесс распределения приоритета:

— ПОВСТАР и передачей разряда данных — СТОП и передачей разряда данных

— ПОВСТАР и СТОП.

Данные конфликты, которые могут возникнуть в этих пунктах, необходимо решать программно !!!!!

И последний блок, который мы еще не рассмотрели — контроль адреса. Проверяет принятый адрес на соответствие значению, находящемуся в старших семи разрядах регистра адреса TWAR. Также он проверяет наличие общих вызовов, если разрешено их распознавание.

При обнаружение корректного адреса он информирует об этом блок управления, который формирует или не формирует подтверждение получения адресного пакета.

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

Формат регистра TWAR

TWA6 TWA5 TWA4 TWA3 TWA2 TWA1 TWA0 TWGCE
R/W R/W R/W R/W R/W R/W R/W R/W
1 1 1 1 1 1 1

TWA6…TWA0 – адрес устройства. В этих разрядах содержится адрес, на который устройство будет отзываться при работе в режиме ведомого. В режиме ведущего эти разряды безразличны.

TWGCE – разрешение распознавание общих вызовов. 1 – устройство отзывается на общие вызовы (пакеты с адресом $00) и на вызовы с адресом находящемся в разрядах TWA6…TWA0.

Как же взаимодействует прикладная программа с модулем TWI ?  Все очень просто — используется прерывание от модуля TWI, которое возникает после каждого события.

Прерывание от модуля TWI, разрешается в регистре управления. Срабатывает по наступлению события в регистре состояния TWCR, в котором записывается код статуса.

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

Выглядит данная операция следующим образом.

SIGNAL (SIG_2WIRE_SERIAL)   либо         SIGNAL (TWI)      //Одно и тоже

{

Как Вы поняли код в прерывании это обработка статусных кодов на какое либо событие. В справочнике и документации описаны все варианты кодов для всех четырех случаев(режимов). Так что можно написать код самому, а можно взять готовые библиотеки. Т.к.

мы с Вами уже научились подключать библиотеки в статье №21, то не будем писать громоздкий код, а возьмем готовый. Как на меня самые распространенные это апноуты от атмел: аппноут №315, где описывается ведущий интерфейс и аппноут №311 – ведомый.

А также библиотека известного набора джентльмена ProcyonAVRlib, автора PascalStang, где описаны все 4-ри режима. Вот этой библиотекой мы и воспользуемся. А именно нам будут необходимы следующие файлы:

avrlibdefs.h–определяются макросы по работе с портами;

avrlibtypes.h – здесь переопределяются типы переменных для переносимости;

global.h — выбирается частота для дальнейших расчетов;

i2cconf.h – определяется размер буферов отправки/приема данных;

i2c.c – непосредственно наш рабочий файл. Единственно что в функции инициализации не забудьте выставить порты SDA и SCL,

void i2cInit (void)

{

        // set pull-up resistors on I2C bus pins

        sbi (PORTC, 5); // i2c SCL 

        sbi (PORTC, 4); // i2c SDA 

        ………………………………..

}

i2c.h – заголовочный файл . Здесь определяются коды состояний, функции и одна структура с номерами режима работы контроллера.

https://www.youtube.com/watch?v=fAo9LOZdTWc

Быстро просморев файлы Вы увидите что код работы в этом интерфейсе не такой простой, а точнее не такой короткий, на противовес тому что мы писали для пассивного устройства, а именно наших часов в статье №29. Но без такой последовательности мы не получим стабильную работу.

Разобраться в коде не сложно, тем более что мы основы рассмотрели в статьях №28 (Шаг №28. Последовательный двухпроводный интерфейс TWI для AVR с одним ведущим) и №29 (Подключаем часы реального времени DS1307 к AVR).

Основные функции, которые мы будем использовать это

void i2cInit (void);  // Инициализация 

//Запись адреса в контроллер на который он будет отзываться я

void i2cSetLocalDeviceAddr (u08 deviceAddr, u08 genCallEn);

//Прием данных ведомым

void i2cSetSlaveReceiveHandler (void (*i2cSlaveRx_func)(u08 receiveDataLength, u08* recieveData));

//Отправка данных ведомым ведущему

void i2cSetSlaveTransmitHandler (u08 (*i2cSlaveTx_func)(u08 transmitDataLengthMax, u08* transmitData));

//Передача данных ведомому

void i2cMasterSend (u08 deviceAddr, u08 length, u08 *data);

//Прием данных ведущим

void i2cMasterReceive (u08 deviceAddr, u08 length, u08* data);

Как я и писал выше в качестве примера рассмотрим проект с двумя микроконтроллерами ATmega8A и часами реального времени. Сравним результат работы в протеусе и  в железе. Дабы не растягивать статью, то применение данной библиотеки, эксперименты, и результат работы перенесем в следующую статью. На этом все. Всем пока.

Просмотрено 1865 раз.

Источник: http://www.ap-impulse.ru/shag-34-twi-sinxronizaciya-i-prioritet-dva-vedushhix-avr-na-shine-ispolzuem-biblioteku/

ATmega8 + аппаратный TWI модуль: “делаем распечатку памяти RTC DS1307”

разделы: I2C , RTC , AVR , дата: 27 сентября 2015г

После UART, реализация TWI модуля на AVR кажется довольно корявой и без понимания I2C протокола будет не просто его освоить. Однако, если разобраться с “софтовой” эмуляцией протокола, то работа с TWI модулем уже не составит труда.

Не последне место в этом списке должно быть у официального аппнота: AVR315: Using the TWI module as I2C master

Если открыть руководство для ATmega8, на странице 156 раздел “Two-Wire Serial Interface”, то там даже будет примерный набросок программы для работы с TWI:

На модуле с DS1307 есть EEPROM AT24 на 36 кБайт. Здесь: “The TWI example project” есть демонстрационая программа, которая распечатывает содержимое EEPROM в консоль UART. Выглядит это как-то так:

Там только в начале исходника нужно поправить #define F_CPU в соответствии с реальной частотой чипа.

Протокол работы c RTC DS1307, прост. Там есть внутрений регистр – счетчик адреса, и оперативка на 64 байт. Отношение к часам имеют только байты с 0х00 по 0х07, остальные на можно использовать по своему усмотрению. Учитывая, что там автономное питание, их можно использовать вместо или в паре с EEPROM.

Итак, чтобы записать данные в DS1307 , нужно сперва послать адрес начальной ячейки, и затем данные по порядку. Счетчик при этом сам индексируется.При достижении счетчиком верхней границы, он сбрасывается в ноль. Т.е. он бегает по кругу.

Чтобы прочитать данные с определенной ячейки памяти RTC нужно сначала открыть I2C сессию на запись, послать адрес, закрыть сессию, и уже потом открывать сесию на чтение, и читать по порядку.

Официальное руководство по DS130764 x 8, Serial, I2C Real-Time Clock

Используя программу из этого поста в качестве шаблона, я добавил к ней поддержку TWI модуля, и сделал так, что бы она распечатывала через UART содержимое всех 64 байт пямяти RTC DS1307

#define F_CPU 16000000UL
#define LEN 32 #include
#include
#include
#include
#include #define START 0x08
#define REP_START 0x10
#define MT_DATA_ACK 0x28 #define MT_ADR_ACK 0x18
#define MT_ADR_NACK 0x20 #define MT_DATA_ACK 0x28
#define MT_DATA_NACK 0x30
#define MT_ARB_LOST 0x38 #define WRITE 0x00
#define READ 0x01 #define READ_END 0x01
#define READ_NOEND 0x00 #define RTC 0xD0 char buffer[LEN];
register unsigned char IT asm(“r16”);
volatile unsigned char done;
volatile unsigned char IDX; static inline void clearStr(char* str)
{ for(IT=0;IT= LEN) { IDX=0; done=1; }
} static void blink13(uint8_t count)
{ PORTB |= (1

Источник: http://www.count-zero.ru/2015/ds1307_twi/

Программная реализация мастер-абонента шины I2C в режиме single-master (библиотека процедур для AVR)

В этой статье рассматривается пример реализации на микроконтроллере AVR мастер-абонента шины I2C в режиме single-master (когда микроконтроллер выступает в роли единственного мастер-абонента на шине). Для понимания механизма функционирования интерфейса I2C, рекомендую сначала ознакомиться с теорией. Если же с теорией вы уже разобрались, тогда можно приступать к практической реализации.

Итак, рассматриваемый режим single-master является самым простейшим случаем, поскольку в этом случае наш контроллер является единственным устройством, которое может управлять линией Clock и формировать старт- и стоп-условия. Соответственно, функции арбитража реализовывать не нужно, никаких коллизий и так возникнуть не может. Всё, что нужно нашему мастер-абоненту для обмена данными со слэйв-устройствами — это уметь делать следующие вещи:

  1. формировать на шине “Старт”-условие;
  2. формировать на шине “Стоп”-условие;
  3. формировать и посылать пакет данных из 8 бит с обработкой ответа (подтверждения) от “Slave”-устройства;
  4. принимать пакет данных из 8 бит с посылкой бита подтверждения и без посылки бита подтверждения.

Каждую из перечисленных “обязанностей” удобно реализовать в виде отдельной процедуры. Для удобства восприятия назовём их, например так: Start_uslovie, Stop_uslovie, Send_byte, Recieve_byte.

Поскольку подключаемые к I2C устройства должны имитировать выходы “с открытым коллектором”, то для реализации этих процедур нам понадобятся ещё 4 процедуры, которые будут непосредственно управлять соответствующими линиями портов, имитируя на них выходы “с открытым коллектором” (по две на каждую линию — Clock и Data). Их назовём так: Clock_null, Clock_one, Data_null, Data_one.

Кроме того, наше “Master”-устройство должно отслеживать такое событие, как “удержание” “Slave”-устройством линии Clock (когда оно по каким-либо причинам “растягивает” передачу данных).

В таблице ниже представлены алгоритмы и программные коды на ассемблере, реализующие каждую из указанных выше процедур. Перечисленные процедуры — это самый низкий уровень протокола, включив их в свой проект и выстроив обращения к ним определённым образом (в соответствии с логикой шины I2C), можно, как из конструктора, собрать весь процесс обмена с любым “Slave”-устройством на шине.

Для удобства нам понадобятся 4 пользовательских регистра: BTS (byte to send) — сюда будет записываться байт, который мы хотим послать, RDB (recieved byte) — сюда будет записываться принятый байт, Bit_counter — будет использоваться в качестве счётчика принятых и посланных бит и последний регистр — I2C_Flags — здесь будут храниться наши служебные флаги.

Собственно, в этом примере, мы будем использовать только один флаг и, соответственно, займём для этого только один нулевой бит.

Переживать, что из целого регистра мы используем всего один бит не стоит, ведь мы рассматриваем простейший пример, но вдруг вы захотите его усложнить или добавите эти процедуры в какую-нибудь сложную программу, тут-то нам целый регистр собственных флагов наверняка пригодится.

Так вот, бит 0 регистра I2C_flags — флаг подтверждения (ACK) и работать он будет следующим образом. Когда мастер принимает данные: если этот бит равен 0, то мастеру нужно выдать подтверждение приёма байта, а если он установлен в 1, то подтверждение выдавать не нужно. Когда мастер посылает данные: если этот бит установился в 0 — подтверждение получено, если в 1 — подтверждение не получено.

Алгоритмы: Программные коды:

Источник: http://radiohlam.ru/?p=1049

Библиотека AVR GCC для управления ЖК индикатором – использование различных выводов микроконтроллера. Часть 2

» Схемы » Применение микроконтроллеров

31-12-2011

Примечание. Выборки исходного кода, размещенные в тексте описания, использовать в своих проектах не рекомендуется. В конце описания имеется ссылка на архив с полным исходным кодом к проекту.

В первой части мы рассмотрели конфигурирование библиотеки AVR-GCC для работы с символьными ЖК индикаторами в 4-битном режиме при подключении к выводам различных портов микроконтроллера. Чтобы убедиться, что библиотека функционирует правильно, рассмотрим пример с подключением индикатора по 8-битной шине. Подключение индикатора осуществим согласно схемы.

Итак, в программе микроконтроллера сперва, как вы помните, нужно установить режим работы:

//используется 4-битный режмим работы ЖК индикатора (при стандартном подключении индикатора к МК) //#define LCD_4BIT //используется 8-битный режмим работы ЖК индикатора (при стандартном подключении индикатора к МК) //#define LCD_8BIT //используется 4-битный режмим работы ЖК индикатора (при использовании разных выводов МК) //#define LCD_4BIT_M //используется 8-битный режмим работы ЖК индикатора (при использовании разных выводов МК) #define LCD_8BIT_M

Далее, нам необходимо связать выводы индикатора с линиями ввода/вывода микроконтроллера:

#define LCD_RS 5 //вывод порта МК 5 подключен к LCD RS #define LCD_RW 1 //вывод порта МК 1 подключен к LCD R/W #define LCD_E 2 //вывод порта МК 2 подключен к LCD E #define LCD_D0 0 //вывод порта МК 0 подключен к LCD D0 #define LCD_D1 7 //вывод порта МК 7 подключен к LCD D1 #define LCD_D2 1 //вывод порта МК 1 подключен к LCD D2 #define LCD_D3 2 //вывод порта МК 2 подключен к LCD D3 #define LCD_D4 4 //вывод порта МК 4 подключен к LCD D4 #define LCD_D5 2 //вывод порта МК 2 подключен к LCD D5 #define LCD_D6 6 //вывод порта МК 6 подключен к LCD D6 #define LCD_D7 0 //вывод порта МК 0 подключен к LCD D7

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

#ifdef LCD_4BIT_M || LCD_8BIT_M //8-битный или 4-битный режим работы (при использовании разных выводов МК) #define LDPRS PORTB //назначаем порт для линии RS и регистр направления данных #define LDDRS DDRB #define LDPRW PORTD //назначаем порт для линии RW и регистр направления данных #define LDDRW DDRD #define LDPE PORTD //назначаем порт для линии E и регистр направления данных #define LDDE DDRD #define LDPD0 PORTС //назначаем порт для линии D0 и регистр направления данных #define LDDD0 DDRС #define LDPD1 PORTD //назначаем порт для линии D1 и регистр направления данных #define LDDD1 DDRD #define LDPD2 PORTС //назначаем порт для линии D2 и регистр направления данных #define LDDD2 DDRС #define LDPD3 PORTB //назначаем порт для линии D3 и регистр направления данных #define LDDD3 DDRB #define LDPD4 PORTD //назначаем порт для линии D4 и регистр направления данных #define LDDD4 DDRD #define LDPD5 PORTC //назначаем порт для линии D5 и регистр направления данных #define LDDD5 DDRC #define LDPD6 PORTD //назначаем порт для линии D6 и регистр направления данных #define LDDD6 DDRD #define LDPD7 PORTB //назначаем порт для линии D7 и регистр направления данных #define LDDD7 DDRB #endif

Конфигурирование библиотеки AVR-GCC для 4-битного режима при стандартном подключении индикатора

В случае стандартного включения ЖК индикатора по 4-битной шине (байт-синхронизированное включение) конфигурирование осуществляется как и в прошлой версии библиотеки. Нужно будет только указать, что мы используем данный режим.

//используется 4-битный режмим работы ЖК индикатора (при стандартном подключении индикатора к МК) #define LCD_4BIT

Далее необходимо указать номера выводов микроконтроллера для сигналов управления и данных индикатора.

#define LCD_RS 0 //вывод порта МК 5 подключен к LCD RS #define LCD_RW 1 //вывод порта МК 5 подключен к LCD R/W #define LCD_E 2 //вывод порта МК 5 подключен к LCD E #define LCD_D0 0 //вывод порта МК 5 подключен к LCD D0 #define LCD_D1 1 //вывод порта МК 5 подключен к LCDD1 #define LCD_D2 2 //вывод порта МК 5 подключен к LCD D2 #define LCD_D3 3 //вывод порта МК 5 подключен к LCD D3 #define LCD_D4 4 //вывод порта МК 5 подключен к LCDD4 #define LCD_D5 5 //вывод порта МК 5 подключен к LCD D5 #define LCD_D6 6 //вывод порта МК 5 подключен к LCD D6 #define LCD_D7 7 //вывод порта МК 5 подключен к LCD D7

Затем определяем порты и регистры управления, как указано ниже.

#if defined (LCD_4BIT)||defined (LCD_8BIT) #define LDP PORTD //определяем порт микроконтроллера, подключенного к шине данных LCD #define LCP PORTD //определяем порт микроконтроллера, подключенного к шине управления LCD #define LDDR DDRD //определяем регистр управления портом микроконтроллера, подключенного к шине данных LCD #define LCDR DDRD //определяем регистр управления портом микроконтроллера, подключенного к шине управления LCD #endif

Эти же операции необходимо выполнить при стандартном подключении индикатора по 8-битной шине.

Следует заметить, что при байт-синхронизированном подключении индикатора обновление индикатора происходит быстрее, т .к. в 4-битном или 8-битном режиме данные принимаются быстрее – операции с целыми байтами или полубайтами.

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

Например, для передачи одного байта с помощью данной библиотеки, при подключении индикатора к различным выводам микроконтроллера, можно использовать данную функцию:

static void LCDMix_8Bit(uint8_t data) { if((data)&(0b10000000)) LDPD7 |=1LCD_D7; else LDPD7 &=~(1LCD_D7); if((data)&(0b01000000)) LDPD6 |=1LCD_D6; else LDPD6 &=~(1LCD_D6); if((data)&(0b00100000)) LDPD5 |=1LCD_D5; else LDPD5&=~(1LCD_D5); if((data)&(0b00010000)) LDPD4 |=1LCD_D4; else LDPD4 &=~(1LCD_D4); if((data)&(0b00001000)) LDPD3 |=1LCD_D3; else LDPD3 &=~(1LCD_D3); if((data)&(0b00000100)) LDPD2 |=1LCD_D2; else LDPD2 &=~(1LCD_D2); if((data)&(0b00000010)) LDPD1 |=1LCD_D1; else LDPD1&=~(1LCD_D1); if((data)&(0b00000001)) LDPD0 |=1LCD_D0; else LDPD0 &=~(1LCD_D0); }

В функции проверяется каждый бит байта и устанавливается или сбрасывается соответствующий вывод порта.

Другими словами, если ваше приложение отображает на индикаторе медленно меняющиеся данные, то подойдет любой режим работы.

Но если вы используете ЖК индикатор/дисплей для динамического отображения данных или анимации, лучше всего выбирать стандартный способ подключения индикатора в 8- или 4-битном режиме.

Загрузки

Библиотека AVR-GCC с поддержкой смешанного подключения ЖК индикатора – скачать
Пример программы для управления индикатором (смешанное подключение, 4-битный режим) – скачать
Пример программы для управления индикатором (смешанное подключение, 8-битный режим) – скачать

Источник: https://www.rlocman.ru/shem/schematics.html?di=112703

Ссылка на основную публикацию
Adblock
detector
",css:{backgroundColor:"#000",opacity:.6}},container:{block:void 0,tpl:"
"},wrap:void 0,body:void 0,errors:{tpl:"
",autoclose_delay:2e3,ajax_unsuccessful_load:"Error"},openEffect:{type:"fade",speed:400},closeEffect:{type:"fade",speed:400},beforeOpen:n.noop,afterOpen:n.noop,beforeClose:n.noop,afterClose:n.noop,afterLoading:n.noop,afterLoadingOnShow:n.noop,errorLoading:n.noop},o=0,p=n([]),h={isEventOut:function(a,b){var c=!0;return n(a).each(function(){n(b.target).get(0)==n(this).get(0)&&(c=!1),0==n(b.target).closest("HTML",n(this).get(0)).length&&(c=!1)}),c}},q={getParentEl:function(a){var b=n(a);return b.data("arcticmodal")?b:(b=n(a).closest(".arcticmodal-container").data("arcticmodalParentEl"),!!b&&b)},transition:function(a,b,c,d){switch(d=null==d?n.noop:d,c.type){case"fade":"show"==b?a.fadeIn(c.speed,d):a.fadeOut(c.speed,d);break;case"none":"show"==b?a.show():a.hide(),d();}},prepare_body:function(a,b){n(".arcticmodal-close",a.body).unbind("click.arcticmodal").bind("click.arcticmodal",function(){return b.arcticmodal("close"),!1})},init_el:function(d,a){var b=d.data("arcticmodal");if(!b){if(b=a,o++,b.modalID=o,b.overlay.block=n(b.overlay.tpl),b.overlay.block.css(b.overlay.css),b.container.block=n(b.container.tpl),b.body=n(".arcticmodal-container_i2",b.container.block),a.clone?b.body.html(d.clone(!0)):(d.before("
"),b.body.html(d)),q.prepare_body(b,d),b.closeOnOverlayClick&&b.overlay.block.add(b.container.block).click(function(a){h.isEventOut(n(">*",b.body),a)&&d.arcticmodal("close")}),b.container.block.data("arcticmodalParentEl",d),d.data("arcticmodal",b),p=n.merge(p,d),n.proxy(e.show,d)(),"html"==b.type)return d;if(null!=b.ajax.beforeSend){var c=b.ajax.beforeSend;delete b.ajax.beforeSend}if(null!=b.ajax.success){var f=b.ajax.success;delete b.ajax.success}if(null!=b.ajax.error){var g=b.ajax.error;delete b.ajax.error}var j=n.extend(!0,{url:b.url,beforeSend:function(){null==c?b.body.html("
"):c(b,d)},success:function(c){d.trigger("afterLoading"),b.afterLoading(b,d,c),null==f?b.body.html(c):f(b,d,c),q.prepare_body(b,d),d.trigger("afterLoadingOnShow"),b.afterLoadingOnShow(b,d,c)},error:function(){d.trigger("errorLoading"),b.errorLoading(b,d),null==g?(b.body.html(b.errors.tpl),n(".arcticmodal-error",b.body).html(b.errors.ajax_unsuccessful_load),n(".arcticmodal-close",b.body).click(function(){return d.arcticmodal("close"),!1}),b.errors.autoclose_delay&&setTimeout(function(){d.arcticmodal("close")},b.errors.autoclose_delay)):g(b,d)}},b.ajax);b.ajax_request=n.ajax(j),d.data("arcticmodal",b)}},init:function(b){if(b=n.extend(!0,{},a,b),!n.isFunction(this))return this.each(function(){q.init_el(n(this),n.extend(!0,{},b))});if(null==b)return void n.error("jquery.arcticmodal: Uncorrect parameters");if(""==b.type)return void n.error("jquery.arcticmodal: Don't set parameter \"type\"");switch(b.type){case"html":if(""==b.content)return void n.error("jquery.arcticmodal: Don't set parameter \"content\"");var e=b.content;return b.content="",q.init_el(n(e),b);case"ajax":return""==b.url?void n.error("jquery.arcticmodal: Don't set parameter \"url\""):q.init_el(n("
"),b);}}},e={show:function(){var a=q.getParentEl(this);if(!1===a)return void n.error("jquery.arcticmodal: Uncorrect call");var b=a.data("arcticmodal");if(b.overlay.block.hide(),b.container.block.hide(),n("BODY").append(b.overlay.block),n("BODY").append(b.container.block),b.beforeOpen(b,a),a.trigger("beforeOpen"),"hidden"!=b.wrap.css("overflow")){b.wrap.data("arcticmodalOverflow",b.wrap.css("overflow"));var c=b.wrap.outerWidth(!0);b.wrap.css("overflow","hidden");var d=b.wrap.outerWidth(!0);d!=c&&b.wrap.css("marginRight",d-c+"px")}return p.not(a).each(function(){var a=n(this).data("arcticmodal");a.overlay.block.hide()}),q.transition(b.overlay.block,"show",1*")),b.overlay.block.remove(),b.container.block.remove(),a.data("arcticmodal",null),n(".arcticmodal-container").length||(b.wrap.data("arcticmodalOverflow")&&b.wrap.css("overflow",b.wrap.data("arcticmodalOverflow")),b.wrap.css("marginRight",0))}),"ajax"==b.type&&b.ajax_request.abort(),p=p.not(a))})},setDefault:function(b){n.extend(!0,a,b)}};n(function(){a.wrap=n(document.all&&!document.querySelector?"html":"body")}),n(document).bind("keyup.arcticmodal",function(d){var a=p.last();if(a.length){var b=a.data("arcticmodal");b.closeOnEsc&&27===d.keyCode&&a.arcticmodal("close")}}),n.arcticmodal=n.fn.arcticmodal=function(a){return e[a]?e[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?void n.error("jquery.arcticmodal: Method "+a+" does not exist"):q.init.apply(this,arguments)}}(jQuery)}var debugMode="undefined"!=typeof debugFlatPM&&debugFlatPM,duplicateMode="undefined"!=typeof duplicateFlatPM&&duplicateFlatPM,countMode="undefined"!=typeof countFlatPM&&countFlatPM;document["wri"+"te"]=function(a){let b=document.createElement("div");jQuery(document.currentScript).after(b),flatPM_setHTML(b,a),jQuery(b).contents().unwrap()};function flatPM_sticky(c,d,e=0){function f(){if(null==a){let b=getComputedStyle(g,""),c="";for(let a=0;a=b.top-h?b.top-h{const d=c.split("=");return d[0]===a?decodeURIComponent(d[1]):b},""),c=""==b?void 0:b;return c}function flatPM_testCookie(){let a="test_56445";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function flatPM_grep(a,b,c){return jQuery.grep(a,(a,d)=>c?d==b:0==(d+1)%b)}function flatPM_random(a,b){return Math.floor(Math.random()*(b-a+1))+a}