В этом материале будет рассмотрен топорный вариант работы с TWI модулем на примере часов реального времени - микросхемы DS1307. Топорный, потому что обмен по I2C будет организован без прерываний и анализа статусных кодов, чтобы сильно не грузить начинающих.
DS1307
Микросхема DS1307 предназначена для счета времени - секунд, минут, часов, дней, месяцев и лет. То есть по сути, это часы с календарем.
Она тактируется от собственного кварцевого генератора с частотой 32768 Гц и может работать от двух источников питания - основного и резервного. Важная фишка этой микросхемы заключается в низком потреблении - меньше 500 nA в рабочем режиме. При таком потреблении DS1307 может проработать от трехвольтовой батарейки (типа CR2032 емкостью ~200 мА*ч) несколько лет. Также DS1307 может генерировать на одном из выводов меандр и в ее составе есть 56 байт оперативной памяти, которую можно использовать для хранения данных.
Минимальная схема подключения DS1307 включает в себя часовой кварц и один источник питания. Обмен данными с микросхемой осуществляется по I2C, причем DS1307 может работать на шине только как подчиненное устройство (слейвом).
С чего начать?
Подключение любой микросхемы начинается с изучения даташита. В случае DS1307 нам нужно выяснить: какую скорость обмена поддерживает микросхема, какой она имеет адрес, как выглядит карта памяти, есть ли у нее управляющие биты или регистры, как передать данные и как прочитать их. Ниже приведены скриншоты из даташита, в которых я нашел требуемую информацию.
Скорость обмена DS1307
Адрес, по которому DS1307 отзывается на I2C шине
Карта памяти DS1307
Карта памяти показывает, по каким адресам расположены регистры микросхемы и сколько их вообще.
По нулевому адресу располагается регистр секунд. Младшие 4 разряда регистра отведены для единиц, там может быть число от 0 до 9. Старшие - для десятков секунд.
Это так называемый двоично-десятичный формат представления чисел (BCD).При таком формате один байт может представить числа только от 0 до 99. Остальные регистры часов и календаря содержат данные в таком же формате.
7-й разряд регистра секунд - управляющий. 0 в этом разряде разрешает работу часов, 1 - запрещает. При подачи питания этот разряд устанавливается в 1.
По первому адресу расположен регистр минут. С ним все понятно.
По второму адресу располагается регистр часов. 6-й бит этого регистра задает формат представления времени. Если он установлен в 1 используется 12 часовой формат, если сброшен -24 часовой.
Далее идут регистры календаря.
По седьмому адресу располагается регистр, управляющий выводом SQW. На него можно выводить внутренний тактовый сигнал разной частоты. Значения битов описаны в даташите. Нас они сейчас не интересуют.
Ну и адреса с 8 по 63-й отведены для оперативной памяти. Их можно использовать для хранения данных.
Как записать данные в DS1307
DS1307 может работать в двух режимах: как подчиненный приемник и как подчиненный передатчик. В первом режиме ведущее устройство передает DS1307 данные, а DS1307 принимает их. Во втором - ведущее устройство принимает от DS1307 данные, а та в свою очередь передает их. (Но обмен в обоих случаях начинает ведущий!)
Для каждого режима в даташите есть описание и диаграмма обмена. Запись данных выполняется согласно следующей последовательности.
1. Ведущий формирует на шине состояние СТАРТ.
2. Ведущий выдает на шину адрес DS1307 с нулевым битом квитирования (адресный пакет), что сигнализирует ведомому о последующей записи данных.
3. Если на шине присутствует микросхема DS1307, она отвечает ведущему - ACK.
4. После получения ответа ведущий передает DS1307 адрес регистра, с которого начнется запись данных. Это значение записывается во внутренний счетчик адреса DS1307.
5. DS1307 снова отвечает ведущему.
6. Получив ответ, ведущий передает ведомому байт данных, который предназначен для записи в регистр DS1307.
7. DS1307 отвечает ведущему.
8. Шаги 6, 7 повторяются несколько раз.
9. Ведущий формирует на шине состояние СТОП.
Адрес, по которому выполняется запись в DS1307, автоматически увеличивается на единицу. Дойдя до последнего адреса, счетчик обнуляется. Записывать можно любое число байт - хоть один, хоть все.
Как прочитать данные из DS1307
1. Ведущий формирует на шине состояние СТАРТ.
2. Ведущий выдает на шину адрес DS1307 с установленным битом квитирования, что сигнализирует ведомому о последующем чтении данных.
3. DS1307 отвечает ведущему.
4. DS1307 передает ведущему байт данных, на который указывает внутренний счетчик адреса.
5. Ведущий отвечает, что принял данные.
6 . Шаги 4, 5 повторяются несколько раз.
7. DS1307 передает ведущему байт данных.
7. Ведущий неформирует ответ DS1307.
8. Ведущий выдает на шину состояние СТОП.
Поскольку чтение данных выполняется по адресу внутреннего счетчика, его значение нужно предварительно инициализировать. Это делается с помощью операции записи, которая обрывается после передачи адреса регистра.
Код
Теперь можно перейти к коду. Нам понадобится минимум три функции:
- функция инициализации,
- функция записи данных,
- функция чтения данных.
Инициализация
#define F_I2C 50000UL
#define TWBR_VALUE (((F_CPU)/(F_I2C)-16)/2)
#if ((TWBR_VALUE > 255) || (TWBR_VALUE == 0))
#error "TWBR value is not correct"
#endif
void RTC_Init(void)
{
TWBR = TWBR_VALUE;
TWSR = 0;
}
Скорость задается с помощью макроса TWBR_VALUE. Здесь используется формула, разобранная в предыдущей части. При компиляции расчетное значение проверяется с помощью директив препроцессора, чтобы убедиться, что оно не выходит за диапазон.
Передача данных
Передача данных начинается с состояния СТАРТ. Чтобы сформировать его, нужно включить TWI модуль, установить бит TWSTA и сбросить флаг прерывания TWINT. Это выполняется в одну строчку, записью в управляющий регистр TWCR.
Когда микроконтроллер выдаст на шину состояние СТАРТ, установится бит TWINT и в статусном регистре TWSR изменится статусный код. Микроконтроллер должен дождаться установки бита TWINT, прежде чем перейдет к следующей операции. Ожидание в нашем случае выполняется циклическим опросом (тупо поллингом .. не путать с троллингом).
/*формируем состояние СТАРТ*/
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
Каждая установка бита TWINT сопровождается определенным статусным кодом в регистре TWSR. По хорошему, мы должны проверять эти коды, чтобы контролировать успешность операций. Но поскольку код у нас торный (учебный), мы не будем этого делать.
Далее на шину нужно выдать адресный пакет. В регистр данных TWDR загружаем адрес, а бит квитирования устанавливаем нулевым. После загрузки адреса сбрасываем бит TWINT, инициируя дальнейшую работу TWI модуля, и дожидаемся, когда она завершится, опрашивая TWINT.
/*выдаем на шину пакет SLA-W*/
TWDR = (DS1307_ADR<<1)|0;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
Посылаем на шину адрес, с которого будет производиться запись в DS1307. Для этого загружаем в регистр данных требуемое значение, сбрасываем бит TWINT и дожидаемся его установки.
/*передаем адрес регистра ds1307*/
TWDR = adr;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
Далее можно гнать остальные данные таким же методом, а когда надоест выдать на шину состояние СТОП.
/*формируем состояние СТОП*/
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
Полный код функции записи будет выглядеть примерно так.
void RTC_SetValue(uint8_t adr, uint8_t data)
{
/*формируем состояние СТАРТ*/
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
/*выдаемна шину пакет SLA-W*/
TWDR = (DS1307_ADR<<1)|0;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
/*передаем адрес регистра ds1307*/
TWDR = adr;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
/*передаем данные или пропускаем*/
if (data != RTC_RESET_POINTER){
/*это чтобы привести данные к BCD формату*/
data = ((data/10)<<4) + data%10;
TWDR = data;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
}
/*формируем состояние СТОП*/
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
}
С помощью этой функции можно производить запись отдельных регистров и инициализировать внутренний регистр адреса DS1307 для последующей операции чтения данных. Пример использования функции есть в тестовых проектах.
Чтение данных из DS1307
Формируем состояние СТАРТ.
/*формируем состояние СТАРТ*/
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
Посылаем на шину адресный пакет - адрес и установленный бит квитирования.
/*выдаемна шину пакет SLA-R*/
TWDR = (DS1307_ADR<<1)|1;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
Получаем данные. Сбрасываем бит TWINT, инициирую работу TWI модуля. Бит TWEA должен быть установлен в 1, чтобы ведущее устройство сигнализировало ведомому о приеме очередного байта.
Когда бит TWINT снова установится в 1, в регистре данных будет байт принятый от ведомого.
/*считываем данные с подтверждением*/
TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
data = TWDR;
Далее можно продолжать чтение или принять данные без подтверждения и выдать состояние СТОП.
/*считываем данные без подтверждения*/
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
data = TWDR;
/*формируем состояние СТОП*/
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
Полный код функции чтения одного байта данный из DS1307 будет выглядеть примерно так.
uint8_t RTC_GetValue(void)
{
uint8_t data;
/*формируем состояние СТАРТ*/
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
/*выдаем на шину пакет SLA-R*/
TWDR = (DS1307_ADR<<1)|1;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
/*считываем данные*/
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
data = TWDR;
/*формируем состояние СТОП*/
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
return data;
}
Тестовый проект для DS1307
Как обычно тестовый проект, объединяющий все выше сказанное. Программа простая. Инициализируем периферию, загружаем в DS1307 начальное значение. Далее в цикле считываем временя и выводим на LCD. Для общения с DS1307 используются всего три функции.
DS1307-as6.rar
DS1307-winavr.rar
DS1307-cv.rar
DS1307-iar.rar
DS1307-proteus.rar
Остальные проекты выложу позже.
В следующих частях будет разбор работы со статусными кодами, а далее работа с TWI модулем через прерывания.
Comments
while(!(TWCR & (1
while(!(TWCR & (1<<TWINT)));
т.е. я правильно понял, что данная конструкция выполняет операцию & над регистром TWCR и ждет что в результате он будет не 0?
Есть мега 16 к порту PORTD.7 подключена кнопка. И мега 8 к порту PORTD.7 подключен светодиод. Две меги подключены через TWI.
При нажатии кнопки мега16 «говорит» меге8 мол кнопка нажата включи сведодиод.
Можно ли разобрать такой пример?
TWDR = (DS1307_ADR<<1)|0; //зачем тут "<<1" ?
Code:
/*это чтобы привести данные к BCD формату*/
data = ((data/10)<<4) + data%10; //можно поподробнее про это преобразование?
TWDR = (DS1307_ADR<<1)|0; //зачем тут "<<1" ?
Так формируется адресный пакет. Он состоит из адреса и R/W бита. Старшие 7 бит - это адрес, а младший бит - это R/W бит. Для этого и нужен сдвиг. Читай предыдущий материл chipenable.ru/index.php/programming-avr/item/194-uchebnyy-kurs-avr-ispolzovaniya-twi-modulya-osnovy-i2c-ch1.html
BCD формат - старшие 4 бита десятки, младшие 4 бита единицы.
Code:
(data/10)<<4
деление на десять дает количество десятков, сдвиг на 4 помещает это число в старшие 4 разряда байта.
Code:
data%10
Это операция деления с остатком. Делим на 10 с остатком, получаем количество единиц.
Работая с микросхемой m24256 на выходе всегда получаю значение 256 независимо от записанных данных
Code:
RTC_SetValue(RTC_HOUR_ADR, 0);
RTC_SetValue(RTC_MIN_ADR, 0);
RTC_SetValue(RTC_SEC_ADR, 0);
TWDR = (DS1307_ADR
RSS feed for comments to this post