Присоединение внешней EEPROM к измерителю температуры и давления
Здесь применяется энергонезависимая память типа АТ24С256. Она имеет структуру EEPROM (т. е. с индивидуальной адресацией каждого байта), но чтобы отличить ее от встроенной EEPROM, в дальнейшем мы будем внешнюю память называть flash (хотя это и не совсем корректно). Последнее число в обозначении означает объем памяти в килобитах, в данном случае это 256 Кбит или 32 768 байтовых ячеек (32 кбайт). Объем памяти в 32 кбайт кажется смешным в сравнении с современными разновидностями flash, которые уже достигают объемов 8 Гбайт, но, во-первых, для наших целей, как вы увидите, этого будет достаточно. Во-вторых, память принципиально больших объемов с интерфейсом ГС не выпускают — слишком он медленный.
Заметки на полях
Чтобы прочесть 32 кбайта со скоростями I2С, потребуется примерно 0,5 мс на каждый байт, т. е. около 16 с. Потому максимальный объем памяти с таким интерфейсом фирмы Atmel, к примеру, составляет 1 Мбит (АТ24С1024). Память с большими объемами представляет собой, во-первых, действительно flash-память (с блочным доступом), во-вторых, выпускается с интерфейсами побыстрее (как, к примеру, AT26DF321 объемом 4 Мбайта с 66-мегагерцевым интерфейсом SPI). Максимальный объем одного кристалла flash-пэмяти, достигнутый на момент написания этих строк — 8 Гбит (Samsung), более емкие устройства (flash-кэрты) представляют собой модули, собранные из нескольких подобных микросхем. Микросхемы с параллельным интерфейсом имеют стандартную разводку выводов и совместимы по выводам с любыми другими микросхемами памяти.
Кстати, найти в продаже микросхемы EEPROM с последовательным доступом (в том числе и использованную в нашей схеме АТ24С256) в корпусе DIP довольно сложно. АТ24С256 (как и упоминающаяся далее АТ24С512 и другие) чаще встречается в миниатюрном корпусе SOIC с 8 выводами. Так как присоединять в простейшем случае приходится всего 4 вывода (2 питания и 2 сигнальных), то даже при ручной разводке это не доставляет больших сложностей.
Время чтения можно сократить, если попробовать «выжать» из I2С все, на что он способен, но непринципиально. Да нам это и не требуется, потому что все равно эти данные мы будем передавать через UART примерно с такими же по порядку величины скоростями.
Адресация тут двухбайтовая, потому под адрес задействуется два регистра (AddrL и AddrH). Мы выбираем r24 и r25 (см. текст процедур в Приложении 5) — почему именно эти, вы увидите далее. Записываемые данные будут храниться в регистре DATA. Эти регистры являются входными переменными как для процедуры записи, так и чтения.
Теперь давайте определимся, что именно мы будем записывать, и на сколько нам хватит этой памяти. Базовый кадр данных у нас будет состоять из четырех байтов значений давления и температуры. Мы можем, конечно, писать и в распакованном BCD-виде, взяв подготовленные для индикации значения, но зачем загромождать память (кадр тогда состоял бы из 6 байтов, не четырех), если коэффициенты пересчета мы знаем (они у нас хранятся в EEPROM), и пересчитать всегда сможем. Если договориться на четырех байтах, то в наши 32 кбайта мы сможем вместить 8192 измерения (на самом деле чуть меньше, как мы увидим, но это несущественно), то есть при трехчасовом цикле (8 измерений в сутки) памяти нам хватит на 1024 суток, или почти на 3 года записей!
Как видите, даже такой объем вполне приемлемый. Если хотите увеличить еще в два раза — возьмите память АТ24С512, ее можно поставить сюда без изменений в схеме (и в программе, кроме задания максимального адреса). Схемотехника серии АТ24 предполагает возможность установки параллельно четырех или восьми таких микросхем (с заданием индивидуального I2С-адреса для каждой), так что при желании объем можно увеличить еще в четыре-восемь раз. Причем использовать, например, две АТ24С512 целесообразнее, чем одну АТС1024, так как для последней адресация усложняется (адрес для объема 128 кбайт содержит 17 бит и выходит за рамки 2-байтового).
Подробности
Микросхемы серии АТ24 имеют два (или три для микросхем с буквой В в конце обозначения, например, АТ24С256В) специальных вывода А0 и А1 (выводы 1 и 2 для 8-выводных корпусов), которые задают индивидуальный I2С-адрес. Если эти выводы ни к чему не подсоединять (как в нашей схеме), то считается, что они подсоединены к логическому нулю. Тогда I2С-адрес микросхемы при записи будет 10100000 в двоичной форме или $А0 в шестнадцатеричной (см. листинг процедур I2С в Приложении 5). Если на указанные выводы адреса подавать сигналы, то старшие 7 бит адреса такой микросхемы будут определяться формулой 10100А1А0. Таким образом, переходом от одной микросхемы к другой можно управлять, если подавать на эти выводы сигнал по дополнительным линиям, которые фактически будут 17-м и 18-м битами адреса.
Для того чтобы записывать исходные значения температуры и давления, нам их придется где-то хранить отдельно, отведя для этого специальные ячейки в SRAM. Сама запись производится очень просто: с каждым байтом мы увеличиваем на единицу содержимое счетчика адресов AddrH: AddrL (командой adiw — именно для этого и выбирались регистры r24 и r25, чтобы ее можно было использовать), «забиваем» нужный байт в регистр DATA, и вызываем процедуру WriteFlash.
Но тут встает две проблемы. Прежде всего, нужно решить, что делать, когда память закончится. Тогда следует либо обнулять ячейки и начинать запись заново, поверх младших адресов, либо, что гораздо красивее, остановить запись, пока содержимое ее не будет прочитано и адрес принудительно не будет обнулен. Поэтому потребуется какой-то флаг, сигнализирующий о том, что настал конец памяти. Причем отвести для этого флага, например, бит в регистре Flag, будет недостаточно: а что будет при сбое питания? Нам придется хранить где-то во встроенной EEPROM и этот флаг и, главное, текущий адрес памяти, иначе данные будут пропадать после каждого отключения питания. А для прибора, который может писать три года подряд, это «несолидно».
А как отсчитывать время, когда производить запись? Для того чтобы метеоданные были полноценными, их нужно привязать ко времени. И тут мы неизбежно приходим к тому, чтобы объединить часы с нашим измерителем. Этим мы займемся чуть далее, потому что использовать сам контроллер в качестве часов, как мы это делали в главе 14, здесь нецелесообразно, слишком много он всего делает такого, что может вызвать сбой в отсчете времени. Придется задействовать внешние часы, но подключение RTC заметно сложнее, чем памяти, потому мы рассмотрим этот вопрос позднее.
А пока, чтобы отработать процедуры обмена по I2С, договоримся, что запись в память у нас будет производиться по прерыванию Timer 1, который больше все равно в измерителе ничем не занят. При 4 МГц тактовой частоты и максимально возможном коэффициенте ее деления 1024, можно заставить Timer 1 срабатывать каждые, например, 15 с, для чего в регистр сравнения придется записать число 58 594 (проверьте!). С такой частотой память, конечно, заполнится очень быстро (32 кбайта — менее чем за 1,5 суток), но это, наоборот, удобно, если стоит задача проверить все наши процедуры.
Итак, записываем в секции определений программы измерителя, там, где адреса SRAM:
…
;Нех-данные — «сырые», без пересчета
.equ Thex = 0х0А ;0А,0В — старший и младший байты температуры
.equ Phex = 0х0С ;0C,0D — старший и младший байты давления
.equ FEnRAM = $0Е ;флаг, если равен $FF, то писать во flash
…
Отдельно запишем адреса в EEPROM (первые восемь у нас заняты коэффициентами):
.equ FEnEE = 0x10 ;флаг если равен $FF, то писать во flash
.equ EaddrL = 0x11 ;младший байт тек. адреса
.equ EaddrH = 0x12 ;старший байт тек. адреса
Обратите внимание, что запись во flash разрешена, если байт FEnEE равен $FF, т. е. в самом начале, когда EEPROM еще пуста, запись по умолчанию разрешается. В процедуре обработки данных дописываем процедуры сохранения «сырых» значений температуры и давления по указанным адресам. Они у нас содержатся в регистрах AregH: AregL. В начале обработки данных по температуре, после имеющегося оператора rjmp prs дописываем:
ldi ZL,Thex ;запоминаем температуру
st Z+,AregH
st Z,AregL
А там, где начинается расчет давления, после оператора rjmp contPT записываем:
ldi ZL,Phex ;запоминаем давление
st Z+,AregH
st Z,AregL
Теперь инициализируем таймер. В загрузочную секцию вместо строк инициализации Timer 0 (ldi temp, (1<
;++++++++Set Timer 1
ldi temp,high(58594)
out OCR1АН, temp
ldi temp,low(58594)
out OCR1AL,temp
ldi temp,0b01000000
out TCCR1A,temp ;переключающий режим для вывода PD5-0C1A
ldi temp,0b00001101
out TCCR1B,temp ;1/1024 очистить после совпадения
ldi temp, (1<
;по совпадению для Timer 1 и переполнению Timer 0
out TIMSK,temp
К выводу OC1A (вывод 19 для ATmega8535) можно присоединить светодиод, который будет попеременно гореть и гаснуть с периодом 30 с, показывая, что запись работает.
Далее в секции начальной загрузки инициализируем регистры адреса. Получится довольно сложная процедура (листинг 16.6), которая должна проверять значения адреса в EEPROM, и если он есть (т. е. память не пуста и там не записаны все единицы), то еще и сравнивать его с последним возможным адресом (32767 или 7FFFh).