Глава 15 Хранить вечно!
В большинстве микроконтроллеров PIC среднего и верхнего уровней имеется отдельная EEPROM-память небольшого объема, управление и доступ к которой осуществляются посредством регистров специального назначения, как и для других периферийных устройств. Наличие встроенной энергонезависимой памяти дает программисту возможность считывать и модифицировать различные статические данные, такие как показания автомобильного одометра, которые должны сохраняться при отсутствии питания (см. Пример 12.3 на стр. 439). Разумеется, для этой цели можно воспользоваться и внешней микросхемой EEPROM, например из линейки 24ХХХ (см. Рис. 12.26 на стр. 439). Однако при небольшом объеме данных, требующих хранения, использование внутренней EEPROM увеличивает надежность устройства и уменьшает его стоимость, габаритные размеры и энергопотребление.
Так что приступим к изучению возможностей этой энергонезависимой памяти. После прочтения этой главы вы:
• Ознакомитесь с характеристиками модуля EEPROM.
• Узнаете, как выполняется чтение/запись данных из/в модуль EEPROM.
• Поймете, каким образом в некоторых моделях микроконтроллеров FLASH-память программ можно использовать для хранения долговременных данных.
• Сможете сравнить модуль EEPROM и FLASH-память программ в качестве устройства хранения долговременных данных.
Устаревший к настоящему моменту микроконтроллер PIC16C84, выпущенный в 1994 году, был первым микроконтроллером PIC, у которого память программ была сделана по технологии EEPROM. Как мы уже видели на Рис. 2.13 (стр. 42), электрически стираемое ППЗУ похоже на обычное СППЗУ (EPROM). однако для его стирания не требуется источник ультрафиолетового излучения. Несмотря на то что технология EEPROM более дорога, чем EPROM, ее использование для реализации памяти программ было оправдано повышенным удобством при разработке опытных образцов устройств, а также при использовании микроконтроллера в учебных целях и радиолюбительской практике. В то же время появился и модуль EEPROM-памяти данных, в котором, отдельно от обычной памяти данных микроконтроллера, могло храниться до 64 байт долговременных данных.
Микроконтроллер PIC16C84 и аналогичная ему модель с FLASH-памятью программ PIC16F84 долгое время оставались единственными представителями в линейке микроконтроллеров PIC, имеющими память EEPROM-типа, — до появления в 1998 году микроконтроллера PIC16F87X. В большинстве своем все микроконтроллеры среднего уровня, выпущенные после 2000 года, были либо совершенно новыми моделями с FLASH-памятью программ, либо аналогами своих предшественников линейки PIC16CXXX с памятью программ EPROM-типа. Все используемые в данной книге модели имеют FLASH-память программ и модуль EEPROM.
Прежде чем приступить к изучению этого модуля, будет полезно ознакомиться с приложениями, требующими использования энергонезависимой памяти. Хорошим примером такого приложения является смарт-карта (см. Рис. 12.1 на стр. 369). В этой карте должны храниться, помимо всего прочего, номер счета, PIN-код, даты начала и конца срока действия карты. Некоторые из этих данных, такие как номер счета, являются по сути дела фиксированными. А защищенные данные могут изменяться пользователем в любой момент времени с помощью терминала. Если карточка используется в качестве банковской, то должна быть предусмотрена возможность записи на счет посредством банкомата информации о доступном кредите, а также изменение этой информации после совершения оплаты. Размеры смарт-карт и требования, предъявляемые к стоимости используемых в них процессоров, таковы, что наличие интегрированной EEPROM-памяти данных жизненно необходимо.
На Рис. 15.1 показана логическая организация модуля EEPROM моделей PIC16F62X[186]. Матрица памяти модуля не имеет никакого отношения к обычным областям памяти программ и памяти данных. Доступ к ней осуществляется посредством четырех РСН, которые используются для указания адреса интересующего нас байта, для хранения считываемого или записываемого байта данных, а также для управления процессами чтения и записи.
Матрица EEPROM-памяти
Модуль EEPROM микроконтроллеров среднего уровня[187] поддерживает до 256 8-битных ячеек. В моделях PIC16F627/8, PIC16F873/4 и PIC12F629/75 реализовано только 128 младших ячеек. В моделях PIC16F648 и PIC16F876/7 реализованы все 256 ячеек. Вот основные параметры этого модуля:
• Не менее 1 000 000 (107 typ) циклов стирания/записи на ячейку при напряжении 5 В и температуре 25 °C[188].
• Максимальная длительность цикла стирания/записи — 8 мс (4 мс typ).
• Срок сохранности данных более 40 лет (100 лет для микроконтроллеров линейки PIC16F62X).
Рис. 15.1. Модуль EEPROM модели PIC16F62X
∙ EEADR (регистр адреса EEPROM)
Восьмибитный регистр может адресовать до 256 (28) байтов. Если в конкретной модели реализована EEPROM-память меньшего объема, то старшие биты адреса должны быть сброшены, чтобы обращение всегда происходило в пределах физического адресного пространства. В микроконтроллерах PIC16F627/8 допустимыми адресами являются адреса из диапазона h’00’…h’7F’.
∙ EEDATA (регистр данных EEPROM)
Регистр данных либо содержит 8-битное число, считанное из EEPROM, либо значение, которое пользователь собирается записать в адресованную ячейку EEPROM-памяти.
∙ EECON1 (регистр управления 1 EEPROM)
Модуль EEPROM может работать в двух режимах — чтение и запись. Управление и контроль процессов чтения и записи осуществляются с помощью регистра EECON1 (см. Рис. 15.2).
Рис. 15.2. Регистр EECON1 модели PIC16F62X
∙ EECON2 (регистр управления 2 EEPROM)
Этот регистр физически не реализован и при его чтении всегда возвращается нулевое значение. Однако он используется для разрешения инициирования операции записи в EEPROM. Для этого в него необходимо загрузить непосредственно друг за другом два числа: сначала Ь’01010101’ (h’55’), а потом b’10101010’ (h’AA’). Эта «пляска с бубном» введена специально, чтобы исключить случайное изменение информации в EEPROM.
Чтобы прочитать данные из EEPROM, необходимо выполнить следующие операции:
1. Загрузить адрес интересующей нас ячейки в регистр EEADR.
2. Установить бит RD для запуска цикла чтения.
3. Бит RD сразу же автоматически сбрасывается, а искомое 8-битное число можно будет в следующем машинном цикле считать из регистра EEDATA.
Подпрограмма EE_GET, код которой приведен в Программе 15.1, реализует описанный алгоритм и возвращает значение из EEPROM в рабочем регистре. Причем это значение остается в регистре EEDATA до повторного использования регистра.
Программа 15.1. Чтение байта из EEPROM
; **************
; * ФУНКЦИЯ: Читает один байт из модуля EEPROM *
; * ВХОД: Адрес в EEADR *
; * ВЫХОД: Значение байта в W и в EEDATA *
; **************
EE_GET bsf STATUS,RP0; Переключаемся в 1-й банк
movlw b’00000001’; Устанавливаем RD для запуска цикла чтения
movwf EECON1; Считываем байт в EEDATA
movf EEDATA,w; Копируем его в W
bcf STATUS,RP0; Возвращаемся в 0-й банк
return; перед возвратом
Процесс записи данных в EEPROM намеренно сделан более запутанным, чтобы уменьшить вероятность случайного запуска цикла записи из-за ошибки в программе или неправильной работы процессора, скажем, по причине сбоя в питании. Запись значения в заданную ячейку осуществляется по следующему алгоритму:
1. Скопировать адрес искомой ячейки в регистр EEADR.
2. Установить бит WREN (EECON1[2]) для разрешения операции записи.
3. Запретить все прерывания.
4. Записать в регистр EECON2 число h’55’.
5. Записать в регистр EECON2 число h’AA’.
6. Установить бит WR для инициирования цикла записи.
7. Сбросить бит WREN.
8. Разрешить прерывания.
9. Дождаться сброса бита WR, свидетельствующего о завершении процесса записи, и выйти из подпрограммы.
Цикл записи не запустится, если операции с номерами 4…6 не будут выполнены непосредственно друг за другом. Так, если во время записи этой кодовой последовательности произойдет прерывание, то операция записи будет прервана. Поэтому перед загрузкой кодовой последовательности необходимо запрещать прерывания, сбрасывая бит GIE.
При необходимости, по завершении цикла записи может генерироваться прерывание. Это прерывание разрешается установкой бита маски EEIE (PIE1[7]). После установки флага прерывания EEIF (PIR1[7]) прерывание генерируется обычным образом. Флаг EEIF должен сбрасываться вручную в обработчике прерывания.
Бывает так, что процессор сбрасывается, скажем, по тайм-ауту сторожевого таймера до завершения цикла записи. В этом случае данные в EEPROM могут оказаться поврежденными. Если операция записи была преждевременно прекращена из-за сброса микроконтроллера, то будет установлен флаг WRERR (EECON[3]). В остальных случаях для повышения надежности данные после завершения цикла записи можно считать обратно и убедиться в их целостности. К этому времени бит WREN можно уже сбросить, чтобы исключить несанкционированную запись. Сброс этого бита до завершения цикла записи не оказывает влияния на операцию.
Приведенный выше алгоритм реализован в Программе 15.2. Значения байта данных и его адреса заносятся в регистры EEDATA и EEADR вызывающей программой. Возврат из подпрограммы происходит только после завершения цикла записи, который длится около 4 мс. Такое решение гарантирует, что указанные РСН не будут изменены во время цикла, что может привести к неверным результатам.
Программа 15.2. Запись байта в EEPROM
; ************************
; * ФУНКЦИЯ: Пишет один байт в модуль EEPROM *
; * ВХОД: Байт данных в EEDATA, адрес байта в EEADR *
; * ВЫХОД: Прерывания запрещены в течение 9 маш. циклов *
; * ВЫХОД: Используется 0-й банк памяти *
; ************************
ЕЕ PUT bsf STATUS,RP0; Переключаемся в 1-й банк
bcf STATUS,RP1
bsf EECON1,WREN; Разрешаем запись
bcf INTCON,GIE; Запрещаем все прерывания
movlw h’55’; Загружаем кодовую последовательность
movwf EECON2
movlw h’AA’
movwf EECON2
bsf EECON1,WR; Инициируем цикл записи
bcf EECON1,WREN; Запрещаем дальнейшие операции записи
bsf INTCON,GIE; Разрешаем прерывания
EE_EXIT btfsc EECON1,WR; Проверяем, запись завершена?
goto EE_EXIT; ЕСЛИ нет, ТО проверяем снова
bcf STATUS,RP0; Возвращаемся в 0-й банк
return; и выходим из подпрограммы по окончании цикла записи
Чтобы проиллюстрировать работу с EEPROM, вернемся к Примеру 12.3 (стр. 439), в котором мы сохраняли 3-байтные показания одометра во внешней последовательной EEPROM. Однако на этот раз мы воспользуемся встроенной EEPROM-памятью. Предположим также, что показания одометра хранятся в ячейках EEPROM с адресами h’10’…h’12’.
В новой программе, код которой приведен в Программе 15.3, для чтения и последующей записи 3-байтного значения одометра из/в модуль EEPROM используются подпрограммы EE_GET и EE_PUT. Адрес первого (старшего) байта в начале подпрограммы копируется в регистр EEADR, а по ходу выполнения подпрограммы для указания на требуемые ячейки этот регистр инкрементируется и декрементируется.
Программа 15.3. Инкрементирование значения одометра, хранящегося в модуле EEPROM
; **********************
; * ФУНКЦИЯ: Инкрементирует 3-байтное значение одометра *
; * РЕСУРСЫ: Подпрограммы EE_GET и EE_PUT *
; * ВХОД: Текущее значение в EEPROM по адресам 10:11:12h *
; * ВЫХОД: Измененное значение в EEPROM по тем же адресам, *
; * ВЫХОД: а также находится в регистрах LSB: NSB: MSB *
; **********************
EXTRA_MILE
bsf STATUS,RP0; Переключаемся в 1-й банк
movlw h’10’; Адрес старшего байта показаний одометра
movwf EEADR; Копируем в регистр адреса EEPROM
call EE_GET; Читаем байт из EEPROM
movwf MSB; и кладем его в регистр MSB
bsf STATUS,RP0; Снова в 1-й банк
incf EEADR,f; Адрес среднего байта показаний одометра
call EE_GET; Читаем байт из EEPROM
movwf NSB; и кладем его в регистр NSB
bsf STATUS,RP0; Снова в 1-й банк
incf EEADR,f; Адрес младшего байта показаний одометра
call EE_GET; Читаем байт из EEPROM
movwf LSB; и кладем его в регистр LSB
; Теперь инкрементируем 3-байтное значение
incf LSB, f; Прибавляем 1
btfss STATUS,Z; Равно нулю?
goto PUT_BACK; ЕСЛИ нет, ТО продолжаем
incfsz NSB, f; Инкрементируем средний байт
goto PUT_BACK; ЕСЛИ не ноль, ТО продолжаем
incf MSB, f
; Помещаем обновленное значение одометра обратно в EEPROM
PUT_BACK movf LSB,w; Берем новое значение младшего байта
bsf STATUS,RP0; Переключаемся в 1-й банк
movwf EEDATA; Кладем его в регистр данных EEPROM
call EE_PUT; Пишем в EEPROM по адресу h’12’
movf NSB,w; Берем новое значение среднего байта
bsf STATUS,RP0; Снова в 1-й банк
movwf EEDATA; Кладем его в регистр данных EEPROM
decf EEADR,f; Адресуем средний байт
call EE_PUT; Пишем в EEPROM по адресу h’11’
movf MSB,w; Берем новое значение младшего байта
bsf STATUS,RP0; Снова в 1-й банк
movwf EEDATA; Кладем его в регистр данных EEPROM
decf EEADR,f; Адресуем старший байт
call EE_PUT; Пишем в EEPROM по адресу h’10’
return
После считывания и копирования 3-байтного значения показаний одометра в память оно инкрементируется точно так же, как и в Программе 12.19 (стр. 442). Обновленное значение затем повторно заносится в EEPROM в обратном порядке, при этом значение регистра EEADR декрементируется. Подпрограмма EE_PUT проверяет завершение цикла записи перед выходом, поэтому в вызывающей программе эту проверку можно не выполнять.
Помимо изменения содержимого EEPROM из программы, ее можно инициализировать при программировании микроконтроллера (при занесении кода программы в память программ), как показано на Рис. 10.6, а (стр. 312). Как мы уже говорили, в памяти данных микроконтроллера имеется специальная область, расположенная по адресам h’2000’…h’30FF’, доступ к которой может осуществляться только в режиме программирования. Из Рис. 10.6, б и Рис. 10.6, в мы видели, что конфигурационный байт расположен по адресу h’2007’. Содержимое модуля EEPROM также находится в этом адресном пространстве по адресам h’2100’…h’21FF’ Поэтому для занесения в EEPROM-память значений функции sin(x) от 0° до 90° с шагом 10° в исходном коде программы должны присутствовать следующие строки:
org h’2100’; Адресное пространство модуля EEPROM
SINE de 0, h’2С’, h’57’, h’7F’, h’A4’, h’C4’
de h’DD’, h’F0’, h’FB’, h’FF’
в которых содержимое EEPROM задается посредством ассемблерной директивы de (Data EEPROM). После занесения программы в микроконтроллер содержимое модуля EEPROM будет выглядеть так, как показано на Рис. 15.3.
Рис. 15.3. Первые 32 байта внутренней EEPROM-памяти, содержащей таблицу значений функции sin(x)
Любые данные, занесенные таким образом в память, могут впоследствии быть считаны программой. Например, чтобы узнать значение sin(50), надо будет прочитать ячейку EEPROM с адресом h’05’ (50/10), в которой хранится число h’C4’ или 196 десятичное (196/256 = 0.76525).
Несмотря на то что память программ можно инициализировать аналогичным образом, используя директиву dw, как это сделано в Программе 15.5, такая возможность используется довольно редко. Это связано с тем, что в соответствии с идеологией гарвардской архитектуры, базирующейся на разделении адресных пространств памяти программ и памяти данных, ни одна из команд не сможет считать эти данные. Команды могут обращаться только к памяти данных. Однако все более-менее современные PIC-микроконтроллеры с FLASH-памятью программ позволяют программам косвенным образом читать и писать эти данные аналогично тому, как это делается при работе с модулем EEPROM. К таким микроконтроллерам, в частности, относятся все модели группы PIC16F87X. Причем между исходными моделями и более поздними версиями с суффиксом «А» имеются определенные различия. Но сначала мы поговорим о первых.
Обе модели PIC16F873/4 имеют FLASH-память программ объемом 4 Кбайт и модуль EEPROM объемом 128 байт, тогда как модели PIC16F876/7 имеют уже 8 Кбайт памяти программ и 256 байт EEPROM. В остальном эти модели полностью идентичны.
Основные характеристики модуля EEPROM микроконтроллеров группы PIC16F87XA:
• Не менее 100 000 (максимум 106) циклов стирания/записи EEPROM-памяти на ячейку.
• Не менее 10 000 (максимум 105) циклов стирания/записи FLASH-памяти программ.
• Максимальная длительность цикла записи/стирания составляет 8 мс (4 мс typ) как для модуля EEPROM, так и для FLASH-памяти.
Хотелось бы обратить внимание на максимальное число циклов перезаписи FLASH-памяти программ (10 000[189]). Несмотря на то что такого значения более чем достаточно при изменении программы устройства, оно накладывает определенные ограничения на использование памяти программ в качестве хранилища долговременных данных. По этой причине FLASH-память программ более пригодна для хранения неизменяющихся данных, таких как таблицы соответствия, нежели для хранения информации, требующей частого обновления, такой как показания одометра.
FLASH-память занимает меньше места на кристалле, чем обычная EEPROM-память. Хотя это и ускоряет процесс записи, однако заряды, которые в конечном счете стекают через изоляцию плавающего затвора, оказывают отрицательное воздействие на механизм хранения и приводят к более раннему ухудшению параметров памяти.
На Рис. 15.4 показан модуль EEPROM модели PIC16F87X вместе с памятью программ. Такое представление справедливо, поскольку регистры EEDATA и EEADR используются для работы с обеими областями памяти. Разумеется, память программ имеет как больший объем (8 Кбайт против 256 байт), так и большую разрядность (14 бит против 8). Именно поэтому в микроконтроллерах были реализованы дополнительные РСН, использующиеся для хранения старшего байта адреса (EEADRH) и данных (EEDATH).
Рис. 15.4. FLASH- и EEPROM-память моделей PIC16F87X как хранилище данных
Как мы скоро убедимся, процессы чтения и записи обеих областей памяти очень похожи. Память, к которой осуществляется обращение, задается управляющим битом EEPGD регистра EECONl (EECONl[7]). За исключением появления этого нового бита и переноса битов EEIF и EEIE в регистры PIR2 и PIE2 соответственно, регистр EECON1, показанный на Рис. 15.4, ничем не отличается от регистра базового варианта модуля из модели PIC16F62X, изображенного на Рис. 15.2. Виртуальный регистр EECON2 остался таким же.
Чтение и запись модуля EEPROM производятся точно так же, как и в более простых моделях PIC16F62X. Единственное изменение, которое необходимо внести в подпрограммы EE_GET и EE_PUT, связано с тем, что в новых моделях регистры EEDATA и EEADR находятся во 2-м банке, а регистры EECONl и EECON2 — в 3-м банке.
Процесс чтения из FLASH-памяти похож на процесс чтения из модуля EEPROM, только при этом используются 2-байтные регистры адреса и данных. Однако не забывайте, что мы работаем с той же памятью программ, откуда коды команд считываются в исполнительный блок процессора. Из-за этого после команды установки бита RD (EECON 1 [0]) должны располагаться две пустые команды пор. Одним словом, чтение FLASH-памяти программ осуществляется по следующему алгоритму:
1. Скопировать адрес интересующей нас ячейки в регистры EEADRH: EEADR.
2. Установить бит EEPGD, показывая, что мы обращаемся к памяти программ.
3. Установить бит RD для запуска цикла чтения.
4. Бит RD сразу же автоматически сбрасывается, и искомое 14-битное значение можно обычным образом считать из регистров 2-го банка памяти EEDATH: EEDATA.
Этот алгоритм реализован в подпрограмме FLASH_GET, текст которой приведен в Программе 15.4. При этом предполагается, что при входе в подпрограмму адрес ячейки уже загружен в регистры EEADRH: EEADR.
Программа 15.4. Чтение слова из FLASH-памяти программ
; *****************
; * ФУНКЦИЯ: Считывает одно слово из FLASH-памяти программ PIC16F877 *
; * ВХОД: Адрес в EEADRH:EEADR *
; * ВЫХОД: Данные в EEDATH:EEDATA. Используется 0-й банк *
; *****************
FLASH_GET
bsf STATUS,RP1; Переключаемся в 3-й банк
bsf STATUS,RP0
movlw b’10000000’; Указываем на память программ,
movwf EECON1; устанавливая EEPGD в EECON1[7]
bsf EECON1,RD; Устанавливаем RD для запуска цикла чтения
nop;
nop;
bcf STATUS,RP1; Возвращаемся в 0-й банк
bcf STATUS,RP0
return
Для примера напишем подпрограмму, которая будет возвращать квадрат целого числа от 0 до 100, загружаемого в регистр EEDATH: EEDATA. Разумеется, эту операцию можно выполнить путем умножения, однако в учебных целях мы реализуем это вычисление при помощи таблицы преобразования, размещенной в памяти программ. Поскольку содержимым этой таблицы являются константы, мы можем загрузить данные в FLASH-память одновременно с занесением всего остального кода программы.
В Программе 15.5 таблица размещается по адресу h’300’ памяти программ. Директива dw похожа на директиву de, однако каждое из значений, указываемых в этой директиве, является 14-битным. Для удобства мы также воспользовались директивой radix, чтобы указать систему счисления констант. В нашем случае все константы интерпретируются ассемблером как десятичные числа.
Сразу же за таблицей располагается исполняемый код. Это делает Программу 15.5 отчасти похожей на класс языка Си++, который содержит в себе как члены-данные, так и члены-функции (подпрограммы).
Программа 15.5. Табличное возведение в квадрат целого числа
radix decima1
__config _CPD_OFF & _WRT_ENABLE_OFF
org h’300’; Таблица начинается с адреса h’300’ памяти программ.
; **************
; * ФУНКЦИЯ: Возвращает квадрат целого числа *
; * РЕСУРСЫ: Подпрограмма FLASH_GET *
; * ВХОД: Целое в W (от 0 до 100) *
; * ВЫХОД: 14-битное значение квадрата в SQRH:SQRL. *
; * Рабочий банк памяти — 0-й *
; **************
TABLE_QF_SQUARES; Таблица десятичных констант
dw 0,1,4,9,16,25,36,49,64,81,100,121,144,169,196,225
dw 256,289,324,361,400,441,484,529,576,625,696,729,784,841
dw 900,961,1024,1089,1156,1225,1296,1369,1444,1521,1600,1681
dw 1764,1849,1936,2025,2116,2209,2304,2401,2500,2601,2704
dw 2809,2916,3025,3136,3249,3364,3481,3600,3721,3844,3969
dw 4049,4225,4356,4489,4624,4761,4900,5041,5184,5329,5476
dw 5625,5776,5929,6084,6241,6400,6561,6724,6889,7056,7225
dw 7396,7569,7744,7921,8100,8281,8464,8649,8836,9025,9216
dw 9409,9604,9801,10000
SQUARE bsf STATUS,RP1; Переключаемся во 2-й банк
bcf STATUS,RP0
movwf EEADR; Формируем адрес
movlw 3
movwf EEADRH
call FLASH_GET; Считываем n-й элемент таблицы
bsf STATUS,RP1; Снова идем во 2-й банк
bcf STATUS,RP0
movf EEDATA,w; Берем младший байт результата
bcf STATUS,RP1; 0-й банк
movwf SQRL; Копируем в SQRL (0-й банк)
bsf STATUS,RP1; Снова идем во 2-й банк
movf EEDATH,w; Берем старший байт результата
bcf STATUS,RP1; 0-й банк
movwf SQRH; Копируем в SQRH (0-й банк)
return
Адрес nn-го элемента таблицы формируется в подпрограмме загрузкой числа nn, переданного в рабочем регистре, в регистр EEADR и записью константы h’03’ в регистр EEADRH. В результате указанных действий мы получаем двухбайтный адрес вида h’3nn’. После этого вызовом подпрограммы FLASH_GET из таблицы считывается 14-битное число. Затем подпрограмма копирует содержимое регистров EEDATH: EEDATA в регистры SQRH: SQRL. Так как эти регистры расположены в 0-м банке, то после копирования содержимого каждого из регистров данных модуля EEPROM, расположенных во 2-м банке, в рабочий регистр, нам приходится переключаться в 0-й банк. Поскольку микроконтроллеры PIC16F874/7 имеют 16 РОН, отображенных на все четыре банка памяти, было бы неплохо разместить регистры SQRH: SQRL именно в этой общей области памяти.
После занесения программы в FLASH-память микроконтроллера внешним программатором содержимое памяти программ начиная с адреса h’300’ будет выглядеть так, как показано на Рис. 15.5.
Рис. 15.5. Фрагмент FLASH-памяти программ, в котором записана таблица преобразования и подпрограмма SQUARE
Несмотря на то что в данном примере положение таблицы было выровнено по 256-байтной границе, на практике она может быть размещена в любом месте памяти. В общем случае для адресации nn-ячейки таблицы к полному 14-битному адресу начала таблицы требуется прибавить смещение nn. Как это можно сделать, обсуждается в Вопросе для самопроверки 15.2.
Процесс записи FLASH-памяти в микроконтроллерах линейки PIC16F87X также практически идентичен процессу записи в EEPROM, отличаясь, как и в случае операции чтения, только двумя командами пор. Правда, после запуска цикла записи выполнение программы приостанавливается примерно на 4 мс. В течение этого времени производится стирание и последующая запись нового значения в адресованную ячейку памяти программ. Затем программа возобновляет работу в нормальном режиме. Итак, запись в FLASH-память осуществляется по следующему алгоритму:
1. Загрузить адрес конечной ячейки в регистры EEADR: EEADRH.
2. Установить бит EEPGD, показывая, что мы обращаемся к памяти программ.
3. Установить бит WREN в EECON[2] для разрешения операции записи.
4. Запретить все прерывания, если они используются.
5. Записать h’55’ в регистр EECON2.
6. Записать h’AA’ в регистр EECON2.
7. Установить бит WR для инициирования цикла записи.
8. Выполнить две пустые команды пор.
9. Сбросить бит WREN.
10. При необходимости разрешить прерывания.
11. Дожидаться сброса бита WR, свидетельствующего о завершении цикла записи, нет необходимости, поскольку на время записи работа процессора приостанавливается и возобновляется только по окончании записи.
Подпрограмма FLASH_PUT, код которой приведен в Программе 15.6, написана в предположении, что при входе в подпрограмму адрес ячейки уже находится в регистрах EEADRH: EEADR, а 14-битное значение — в регистрах EEDATH: EEDATA.
Программа 15.6. Запись в FLASH-память программ
; ************************
; * ФУНКЦИЯ: Записывает одно слово в FLASH-память программ *
; * ВХОД: Слово данных в EEDATH: EEDATA *
; * ВХОД: Адрес ячейки в EEADDRH: EEADDR *
; * ВХОД: На время записи прерывания запрещаются *
; * ВЫХОД: Рабочий банк — 0-й *
; *************************
FLASH_PUT
bsf STATUS,RP0; Переключаемся в 3-й банк
bsf STATUS,RP1
bsf EECON1,EEPGD; Пишем в память программ
bsf EECON1,WREN; Разрешаем операцию записи
bcf INTCON,GIE; Запрещаем все прерывания
movlw h’55’; Загружаем кодовую последовательность
movwf EECON2;
movlw h’AA’
movwf EECON2
bsf EECON1,WR; Инициируем цикл записи
nop
nop
bcf EECON1,WREN; Запрещаем последующую запись
bsf INTCON,GIE; Разрешаем прерывания
bcf STATUS,RP1; Возвращаемся в 0-й банк
bcf STATUS,RP0
return; и выходим из n/n по окончании цикла записи
Все устройства, имеющие память программ с возможностью электрического стирания, содержат в слове конфигурации биты защиты кода. Основной задачей функции защиты кода является предотвращение считывания содержимого памяти программ внешним программатором. Таким образом, обеспечивается защита от несанкционированного доступа к коду программы. Что касается моделей PIC16F87X, в них за защиту кода программы отвечают два бита слова конфигурации СР[1:0], расположенные в битах 13:12 и продублированные в битах 5:4. При СР = 00 защищена вся память программ, при СР = 01 — только старшая половина памяти, при СР = 10 — только старшие 256 байт, а при СР = 11 защита полностью отключена (состояние битов по умолчанию, см. Рис. 10.6, в на стр. 312). Если хоть какая-нибудь область памяти программ защищена, то внешний программатор не сможет выполнить запись ни в одну из ее ячеек. При этом чтение запрещено только для защищенных областей. В процессе разработки и отладки устройств защита памяти программ обычно отключается, поскольку на этом этапе предполагается частое изменение содержимого памяти программ. Если же потребуется снять защиту, то у моделей с FLASH-памятью можно стереть все содержимое памяти программ, используя внешний программатор, при этом в слово конфигурации будет записано значение по умолчанию (во всех битах — 1). Такая возможность, по определению, отсутствует в микроконтроллерах PIC16CXXX.
Защита кода также влияет и на внутренние операции записи в память программ с помощью кода, подобного представленному на Рис. 15.6. Из самой программы запись может осуществляться только в незащищенные участки памяти программ при условии, что бит WRT установлен в 1 (состояние по умолчанию). Запись 0 в этот бит (_WRT_ENABLE_OFF) запретит внутреннюю запись в память программ независимо от установок битов защиты кода. На операцию внутреннего чтения биты защиты кода никак не влияют. Директива __config, присутствующая в Программе 15.5, используется для отключения защиты всей памяти программ, что, вообще говоря, делать не обязательно, поскольку в таком состоянии биты находятся по умолчанию.
Рис. 15.6. Конфигурационное слово моделей PIC16F87XA
В моделях с суффиксом «А» защита содержимого памяти программ осуществляется несколько иначе, как можно увидеть из Рис. 15.6. В этих моделях имеется единственный бит защиты СР, предназначенный для защиты всей памяти программ от считывания или от записи извне. Причем даже при включенной защите памяти программ внутренние операции записи и чтения FLASH-памяти разрешены. Для предотвращения внутренней записи в указанные выше участки памяти программ используются два бита WRT[1:0]. Внутренние операции чтения памяти программ разрешены всегда.
За исключением самой старой модели PIC16F84, во всех микроконтроллерах с модулем EEPROM имеется бит CPD, при записи 0 в который запрещается доступ извне к внутренней EEPROM-памяти данных.
Модели группы PIC16F87X могут осуществлять запись в память программ отдельными словами. Однако в микроконтроллерах PIC16F87XA внутренняя организация FLASH-памяти программ была изменена. Вследствие этого запись в память программ указанных моделей осуществляется блоками по 4 подряд идущих слова. Младшие биты адреса первого слова блока должны быть равны 00. К примеру, если программист собирается записать новое 14-битное слово в память программ по адресу h’500’, ему также придется выполнить запись по адресам h’501’, h’502’ и h’503’. В процессоре имеется четыре внутренних 14-битных буферных регистра, как показано на Рис. 15.7. При каждой операции записи данные просто копируются в соответствующий буфер, определяемый значением двух младших битов адреса. После записи в последний буферный регистр при EEADR[1:0] = 11 (в нашем примере это соответствует адресу h’503’) блок из четырех слов по адресам h’500’.h’503’ стирается, а затем содержимое всех четырех буферных регистров одновременно заносится в память программ.
Внутренние буферные регистры недоступны из программы при помощи обычных команд пересылки данных. Вместо этого запись каждого слова блока осуществляется точно так же, как запись отдельного слова, реализованная в подпрограмме FLASH_PUT (Программа 15.6). При выполнении первых трех операций записи данные просто сохраняются в буферных регистрах, а задержки длительностью 4 мс не происходит. Вызов этой же подпрограммы с адресом, равным последнему адресу блока, запускает «реальную» запись с приостановкой работы процессора.
Рис. 15.7. Запись в FLASH-память программ в моделях PIC16F87XA
Чтобы проиллюстрировать процедуру блочной записи, рассмотрим следующий пример. Имеется четыре 2-байтных значения, размещенные в РОН с названиями DATA_ARRAY…DATA_ARRAY+7 в порядке от старшего байта к младшему, которые необходимо записать в память программ микроконтроллера PIC16F877A.
Подпрограмма, код которой приведен в Программе 15.7, написана в предположении, что данные уже находятся в ОЗУ и что адрес первой из ячеек памяти программ уже занесен в регистры EEADRH: EEADR. Запись каждого слова в буферные регистры и инициирование цикла записи осуществляются в цикле. В качестве указателя для работы с массивом в памяти данных используется регистр FSR, как это было показано на Рис. 5.8 (стр. 126). Каждое двухбайтное значение по очереди копируется в регистры EEDATH: EEDATA, после чего подпрограмма FLASH_PUT из Программы 15.6 запускает цикл псевдозаписи. После каждого прохода цикла адрес ячейки памяти программ, находящийся в регистре EEADR, инкрементируется (EEADRH не трогаем). После четвертого прохода запускается реальный цикл записи. К сожалению, в регистре EECONl отсутствует флаг, по которому можно было бы отличить этот цикл записи от трех предыдущих. Вместо этого мы проверяем состояние двух младших битов регистра EEADR. Когда они снова становятся равными Ь’00’, процесс завершается.
Несмотря на то что записывать необходимо полный блок, можно изменять значение только одного, двух или трех слов блока. Для этого все данные, которые нужно оставить неизменными, сначала считываются из памяти программ в память данных, а затем записываются вместе с изменяемыми данными.
Программа 15.7. Блочная запись FLASH-памяти программ в моделях PIC16F87XA
; **************************
; ФУНКЦИЯ: Пишет блок из 4-х слов в память программ *
; ВХОД: Начальный адрес блока в EEADRH:ADDR *
; ВХОД: Четыре слова в массиве DATA_ARRAY:8 *
; ВЫХОД: Четыре слова записаны в память программ *
; ВЫХОД: Рабочий банк — 0-й *
; РЕСУРСЫ: Подпрограмма FLASH_PUT *
; ***************************
FLASH_BLAST
bsf STATUS,RP1; Переключаемся во 2-й банк
bcf STATUS,RP0
movlw DATA_ARRAY; Загружаем в FSR адрес
movwf FSR; младшего байта массива данных в ОЗУ
; Теперь выполняем 4 цикла записи —
FB LOOP movf INDF,w; Считываем старший байт слова и
movwf EEDATH; помещаем его в старший регистр данных
incf FSR,f; Указываем на младший байт
movf INDF,w; Считываем младший байт слова и
movwf EEDATA; помещаем его в младший регистр данных
incf FSR, f; Указываем на старший байт следующего слова
call FLASH_PUT; Пишем в буферный регистр
bsf STATUS,RP1; Снова переключаемся во 2-й банк
bcf STATUS,RP0
incf EEADR,f; Инкрементируем адрес в памяти программ
movf EEADR,w; Проверим младшие биты на равенство 00
andlw b’00000011’; Выделяем эти биты
btfss STATUS,Z; ЕСЛИ оба равны нулю, ТО выходим
goto FB_LOOP; ИНАЧЕ пишем следующее слово
bcf STATUS,RP1; Возвращаемся в 0-й банк
return; выходим по окончании цикла записи
Примеры
Пример 15.1
В компиляторе CCS имеются следующие встроенные функции для работы с модулем EEPROM:
read_eeprom(<адрес>);
Возвращает байт, находящийся по указанному адресу EEPROM.
write_eeprom(<адрес>, <данные>);
Заносит значение, переданное во втором параметре, по указанному адресу EEPROM (первый параметр). Возврат из функции происходит только после завершения цикла записи.
Напишите функцию на языке Си, которая бы обновляла показания одометра, хранящиеся в EEPROM, аналогично Программе 15.3.
Решение
Как и в исходной ассемблерной программе, код которой приведен в Программе 15.3, новая функция (см. Программу 15.8) состоит из трех частей:
1. На этом этапе объявляется массив из 3 байт, названный odometer [], который служит в качестве временного хранилища показаний одометра, содержащихся в EEPROM. Массив заполняется с помощью трех вызовов функции read_eeprom ().
2. После загрузки 3-байтного значения в память данных оно инкрементируется с использованием оператора выбора if-else:
а) Инкрементируется младший байт и проверяется на нулевое значение. Если он не равен нулю, операция инкрементирования завершается, в противном случае происходит переход к обработке среднего байта.
б) Инкрементируется средний байт и проверяется на нулевое значение. Если он не равен нулю, операция инкрементирования завершается, в противном случае происходит переход к обработке старшего байта.
в) Инкрементируется старший байт.
3. В заключение каждый байт заносится обратно в EEPROM с помощью функции write_eeprom ().
Программа 15.8. Инкрементирование показаний одометра на Си
void odometer(void)
{
unsigned int odometer[3]; /* Объявляем З-байтный массив */
odometer[0] = read_eeprom(0x10); /* Считываем текущее значение */
odometer[1] = read_eeprom(0x11);
odometer[2] = read_eeprom(0x12);
/* Инкрементируем число, находящееся в массиве */
if(++odometer[0]!= 0)
break;
else
if(++odometer[1]!= 0)
break;
else
odometer[2]++;
/* Теперь заносим инкрементированное значение в EEPROM */
write_eeprom(0x10, odometer[0]);
write_eeprom(0x11, odometer[1]);
write_eeprom(0x12, odometer[2]);
}
Если сравнить размеры ассемблерного кода самостоятельно написанной Программы 15.3 и сгенерированного при компиляции Программы 15.8 (для PIC16F62X), то можно увидеть, что при ручном кодировании размер программы получается практически в 2 раза меньше (54 команды против 105).
Пример 15.2
Рассмотрим контроллер сауны, построенный на базе микроконтроллера PIC. Задачей такого контроллера является контроль температуры и управление нагревателем и охладителем. Кроме того, в нем должна быть предусмотрена тревожная сигнализация и возможность экстренного отключения в случае перегрева.
В принципе для построения такого контроллера можно использовать 8-выводной микроконтроллер с внешним датчиком температуры. Однако кто-то предложил в качестве дешевого, хотя и довольно любительского датчика температуры использовать встроенный сторожевой таймер микроконтроллера, а точнее, зависимость его периода от температуры.
Были исследованы 8 экземпляров микроконтроллеров из одной партии, причем при каждой контрольной температуре они выдерживались по 30 мин. В результате был построен график, показанный на Рис. 15.8. Каждая точка этого графика была получена усреднением 500 периодов сторожевого таймера при данной температуре.
Рис. 15.8. Экспериментальная зависимость периода сторожевого таймера от температуры
Данные, представленные на Рис. 15.8, базируются на документе AN720 «Measuring Temperature Using the Watch Dog Timer (WDT)». Два графика представляют максимальное и минимальное значение периода сторожевого таймера тестируемых устройств. Измерение периода сторожевого таймера производилось по числу переполнений Таймера 0, работающего от внутреннего сигнала 4 МГц. К сторожевому таймеру был подключен постделитель с коэффициентом деления 8.
Из приведенных графиков можно заметить, что между величиной периода и температурой существует четкая корреляция. Однако, несмотря на предсказуемость общего характера зависимости, значения смещения и крутизны характеристики будут своими у каждой модели. К примеру, коэффициент пропорциональности у всех восьми протестированных устройств колеблется от 2.28 до 2.42 отсчета на градус Цельсия. Поэтому перед использованием системы ее необходимо будет калибровать. Если для какого-либо конкретного устройства известно количество отсчетов при заданной температуре T0 и коэффициент пропорциональности k, то величину отклонения от температуры T0, соответствующую количеству отсчетов COUNTn, можно будет определить по формуле
ΔТ = (COUNTn — COUNT0) x k.
Для калибровки этих устройств было решено выдерживать партию в холодильнике при 0 °C и записывать 2-байтное значение отсчетов в модуль EEPROM. Затем устройства подвергались нагреву до 30 °C, после чего разность между новым и исходным значением запоминалась в отдельном байте EEPROM. После этого осуществлялось перепрограммирование микроконтроллеров — вместо программы калибровки в память программ заносилась рабочая программа, вычисляющая по значению периода сторожевого таймера COUNTn текущую температуру:
Т = (COUNT0 — COUNTn) x (Diff/30),
где COUNT0 — 2-байтное значение из EEPROM, содержащее количество отсчетов при 0 °C, a Diff — 1-байтное значение из EEPROM, характеризующее изменение количества отсчетов при увеличении температуры до 30 °C.
Покажите, как можно написать программу калибровки, реализующую указанный алгоритм.
Решение
Можно выделить пять задач, которые необходимо выполнять при наступлении тайм-аута сторожевого таймера:
• Усреднение текущего значения числа переполнений Таймера 0 с предыдущими значениями, полученными при низкой температуре (0 °C), за исключением первого отсчета.
• Усреднение текущего значения числа переполнений Таймера 0 с предыдущими значениями, полученными при высокой температуре (30 °C), за исключением первого отсчета.
• Запоминание значения, соответствующего низкой температуре, в EEPROM.
• Вычисление разности между значениями для высокой и низкой температур и запоминание ее в EEPROM.
• Вход в бесконечный пустой цикл.
Для указания, какая из четырех активных задач должна выполняться, можно использовать линии порта ввода/вывода микроконтроллера. Так, наличие ВЫСОКОГО уровня на выводе GPIO0 означает, что текущее число переполнений Таймера 0 необходимо прибавить к уже имеющемуся 2-байтному значению для низкой температуры. После каждого сложения, за исключением первого, результат необходимо усреднить делением на два. Этот ВЫСОКИЙ уровень должен удерживаться на выводах GPIO0 всех микроконтроллеров партии в течение нескольких минут после установления температуры холодильника на уровне 0 °C.
Подача на GPIO0 НИЗКОГО, а на GPIO1 — ВЫСОКОГО уровней на короткое время вызывает сохранение вычисленного значения в EEPROM. При подаче НИЗКОГО уровня на оба входа никаких действий не производится. Это состояние соответствует времени перед стабилизацией температурного режима и времени после программирования EEPROM,
Таймер 0 и сторожевой таймер инициализируются одновременно при сбросе по включению питания. Эта операция будет выполнена лишь один раз при подаче питания на микроконтроллер, уже помещенный в термокамеру. Все последующие сбросы будут происходить по тайм-ауту сторожевого таймера. Для определения причины сброса можно использовать флаг ТО регистра STATUS (см. стр. 453).
Из текста Программы 15.9 видно, что переход к секции кода, в которой выполняется инициализации таймеров и переменных, производится только в том случае, если флаг при сбросе установлен в 1, т. е. при включении питания. После этого программа входит в бесконечный цикл, организованный командой goto $ (ассемблер заменяет символ $ текущим адресом команды), которая просто переходит сама на себя.
Программа 15.9. Процедура инициализации и обработчик прерывания для программы калибровки контроллера сауны
include "p12f629.inc”
__config _WDT_ON & _CP_OFF & _INTRC_OSC_NOCLKOUT & _MCLRE_OFF
cblock h’20’
_work 1, _status:1
FIRST_HI:1, FIRST_LO:1
ROLL_OVER:2, LO_TEMP:2, HI_TEMP:2
DELTA _TEMP:1
endc
org 0
START goto MAIN
org 4
goto ISR
; При каждом сбросе инициализируем Таймер 0, порт ввода/вывода и пр.
MAIN movlw h’07’; Выключаем аналоговый компаратор
movwf CMCON
movlw b’11011010’; Предделитель — к WDT, коэффициент 1:8
bsf STATUS,RP0; Переключаемся в 1-й банк
movwf OPTION_REG; Таймер 0 тактируется внутренним сигналом
bcf STATUS,RP0; Возвращаемся в 0-й банк
clrf TMR0; Обнуляем таймер
bsf INTCON,T0IE; Разрешаем прерывание от Таймера 0
bsf INTCON,GIE; Разрешаем все прерывания
btfss STATUS,NOT_TO; ЕСЛИ тайм-аут сторожевого таймера,
goto READING; ТО идем на обработку значений
; ИНАЧЕ это был сброс по питанию
clrf ROLL_OVER+1; Обнуляем 2-байтный счетчик переполнений Таймера 0
clrf ROLL_OVER
clrf FIRST_HI
clrf FIRST_LO
clrf LO_TEMP+1; Обнуляем регистры результата для низкой температуры
clrf LO_TEMP
clrf HI_TEMP+1;и для высокой температуры
clrf HI_TEMP
clrwdt;Сбрасываем сторожевой таймер
goto $;Ждем следующего сброса от сторожевого таймера
; *******************************
; * В обработчике прерывания при прерывании от Таймера 0 *
; * инкрементируем 2-байтное число переполнений таймера *
; *******************************
;Сначала сохраняем контекст
ISR movwf _work; Сохраняем W
swapf STATUS,w; и регистр STATUS
movwf status
; *******************************
; Основной код обработчика
incf ROLL_OVER+1,f; Регистрируем новое переполнение
btfsc STATUS,Z; Пропускаем, если не ноль
incf ROLL_OVER,f; Инкрементируем старший байт
bcf INTCON,TOIF; Сбрасываем флаг прерывания
; ********************************
swapf _status,w; Восстанавливаем исходное значение
movwf STATUS; регистра STATUS
swapf _work,f; Восстанавливаем исходное значение W
swapf _work,w; не меняя STATUS
retfie; и выходим из прерывания
В этой же программе приведен код процедуры обработки прерывания от Таймера 0. В обработчике осуществляется инкрементирование 2-байтной переменной, расположенной в регистрах ROLL_OVER: ROLL_OVER+1, которая представляет собой числовое выражение длительности периода сторожевого таймера, считываемое системой при сбросе по тайм-ауту сторожевого таймера.
Во время работы программы процессор будет периодически сбрасываться по тайм-ауту сторожевого таймера. А поскольку при этом событии бит сбрасывается, будет выполняться переход к секции reading, код которой приведен в Программе 15.10. В этой секции проверяется состояние каждого из четырех входов GPIO[3:0]. При обнаружении на каком-либо входе ВЫСОКОГО уровня выполняется одна из четырех описанных выше задач. Если на всех выводах присутствует НИЗКИЙ уровень, то программа просто очищает регистры ROLL_OVER и ROLL_OVER+1, перезапускает Таймер 0 и сторожевой таймер, после чего входит в бесконечный цикл. Эти операции, обозначенные меткой reading_exit, выполняются в конце всех четырех задач.
Программа 15.10. Считывание нового значения количества периодов
; *******************************
; * Сюда попадаем при сбросе по тайм-ауту сторожевого таймера *
; *******************************
READING btfsc GPIO,0; Новое значение при низкой температуре?
goto NEW_LO; ЕСЛИ да, ТО переходим на эту секцию!
btfsc GPIO,1; Новое значение при низкой температуре?
goto NEW_HI; ЕСЛИ да, ТО переходим на эту секцию!
btfsc GPIO,2; Обновление среднего для низкой температуры?
goto UPDATE_LO; ЕСЛИ да, ТО переходим на эту секцию!
btfsc GPIO,3; Обновление разности для высокой температуры?
goto UPDATE_HI; ЕСЛИ да, ТО переходим на эту секцию!
goto READING_EXIT; ИНАЧЕ ничего не делаем
NEW_LO movf ROLL_OVER+1,w; Берем младший байт текущего числа переполнений
addwf LO_TEMP+1,f; и прибавляем его к младшему байту накопленного значения
btfsc STATUS,С; Проверяем перенос
incf LO_TEMP,f; ЕСЛИ был, ТО учитываем его
movf ROLL_OVER,w; Берем старший байт текущего числа переполнений
addwf LO_TEMP,f; и прибавляем его к старшему байту накопленного значения
movf FIRST_LO,f; Это был 1-й отсчет?
btfsc STATUS,Z
goto FIRST_TIME_LO; ЕСЛИ да, ТО отметим это!
rrf LO_TEMP,f; ИНАЧЕ делим сумму на два
rrf LO_TEMP+1,f
goto READING_EXIT; и выходим
FIRST_TIME_LO; При первом отсчете ничего не делаем
incf FIRST_LO,f; Первый отсчет уже был
goto READING_EXIT
NEW_HI movf ROLL_OVER+1,w; Берем младший байт текущего числа переполнений
addwf HI_TEMP+1,f; и прибавляем его к младшему байту накопленного значения
btfsc STATUS,С; Проверяем перенос
incf HI_TEMP,f; ЕСЛИ был, ТО учитываем его
movf ROLL_OVER,w; Берем старший байт текущего числа переполнений
addwf HI_TEMP,f; и прибавляем его к старшему байту накопленного значения
movf FIRST_HI,f; Это был 1-й отсчет?
btfsc STATUS,Z
goto FIRST_TIME_HI; ЕСЛИ да, ТО отметим это!
rrf HI_TEMP,f; ИНАЧЕ делим сумму на два
rrf HI_TEMP+1,f
goto READING_EXIT; и выходим
FIRST_TIME_HI; При первом отсчете ничего не делаем
incf FIRST_HI,f; Первый отсчет уже был
READING_EXIT; Сбрасываем Таймер 0
clrf TMR0; Перезапускаем сторожевой таймер
clrwdt
clrf ROLL_OVER+1; Обнуляем число переполнений
clrf ROLL_OVER
goto $; Ждем следующего сброса от сторожевого таймера
Также в Программе 15.10 приведены секции кода, соответствующие первым двум задачам. В этих секциях 2-байтное количество переполнений Таймера 0 прибавляется к значению, хранящемуся в регистрах LO_TEMO: LO_TEMP+1 или Н1_ТЕМР:Н1_ТЕМР+1 соответственно, после чего для усреднения результат делится на два сдвигом на один бит вправо. Поскольку суммарное число переполнений достаточно скромное, двух байтов вполне достаточно, чтобы избежать переполнения. Если мы будем в течение нескольких минут многократно выполнять указанные операции, то в результате получим усредненное значение.
Если считывание результата производится в первый раз, то операция деления на два пропускается, а в соответствующую переменную-флаг FIRST_LO или FIRST_HI заносится ненулевое значение.
Основная процедура, относящаяся к теме данной главы, приведена в Программе 15.11. При ВЫСОКОМ уровне на выводе GPIO2 2-байтное число переполнений при низкой температуре LO_TEMP: LO_TEMP+1 заносится в два младших байта EEPROM с использованием подпрограммы ee_put из Программы 15.2.
При ВЫСОКОМ уровне на выводе GPIO3 вычисляется разность между 2-байтными значениями, полученными при высокой и низкой температурах. Если посмотреть на график, приведенный на Рис. 15.8, то можно заметить, что разность отсчетов при изменении температуры на 30 °C в любом случае не превысит 256, так что для хранения этой разности достаточно будет одного байта. Данное значение сохраняется в EEPROM обычным образом.
Программа 15.11. Обновление информации в EEPROM
UPDATE_LO
movf LO_TEMP,w; Берем старший байт значения для низкой температуры
bsf STATUS,RP0; Переключаемся в 1-й банк
movwf EEDATA; Кладем в регистр данных EEPROM
clrf EEADR; Адрес в EEPROM — h’00’
call EE_PUT; Запоминаем значение
movf LO_TEMP+1,w; Берем младший байт значения для низкой температуры
bsf STATUS,RP0; Переключаемся в 1-й банк
movwf EEDATA; Кладем в регистр данных EEPROM
incf EEADR,f; Адрес в EEPROM — h’01’
call EE_PUT; Запоминаем значение
goto READING_EXIT
UPDATE_HI; Вычисляем HI_TEMP-LO_TEMP и сохраняем разность в EEPROM
; Достаточно вычесть только младшие байты, поскольку разность не может быть больше 256
movf HI_TEMP+1,w; Берем старший байт значения при высокой
subwf L0_TEMP+1,w; Вычитаем младший байт значения при низкой температуре
movwf DELTA_TEMP; температуре Запоминаем разность
bsf STATUS,RP0; Переключаемся в 1-й банк
movwf EEDATA; Разность — по адресу h’02’
movlw 2
movwf EEADR
call EE_PUT
goto READING_EXIT
Пример 15.3
В Примере 14.3 мы вычисляли энергию разряда дефибриллятора путем суммирования квадратов отклонений напряжения от базового значения. Причем после анализа графика в качестве базового было принято значение 2.6 В. Это среднее значение может изменяться от экземпляра к экземпляру прибора, а также с течением времени. Поэтому было решено доработать программное обеспечение, введя в него возможность самообучения, которое будет осуществляться, скажем, при замыкании кнопки, подключенной к выводу RA4. При нажатии на кнопку надо будет выполнить 256 выборок значений напряжения в режиме ожидания, с последующим их сложением для получения 2-байтной суммы. Взяв старший байт ЭТОЙ суммы, МЫ получим усредненное значение напряжения (взятие старшего байта 2-байтного числа эквивалентно его делению на 256). Это значение мы запишем в EEPROM по адресу h’00’ и впоследствии будем использовать в качестве базового уровня, периодически обновляя его при необходимости. Предполагая, что в вашем распоряжении имеется подпрограмма GET_ANALOG из Программы 14.1 (стр. 516), напишите соответствующую подпрограмму.
Решение
Из Рис. 14.20 на стр. 534 видно, что напряжение с датчика тока дефибриллятора подается на вывод RA0/AIN0 микроконтроллера. С учетом того что модуль АЦП уже инициализирован, как это было сделано в Программе 14.6 на стр. 536, нам останется только 256 раз считать оцифрованное значение с 0-го канала АЦП для накопления 16-битной суммы. Взяв старший байт этой суммы, мы получим усредненное значение, т. е. частное от деления суммы на 256. Если во время взятия отсчетов дефибриллятор находился в режиме ожидания, то полученное среднее значение будет представлять собой базовое напряжение.
После определения базового напряжения это однобайтное значение можно записать в EEPROM обычным образом. Впоследствии его можно будет считать из EEPROM и использовать вместо константы BASELINE (Программа 14.6).
В Программе 15.12 регистр COUNT используется в качестве счетчика итераций цикла. В каждом проходе цикла новое значение АЦП прибавляется к общей сумме, накапливаемой в регистрах ACCUMULATOR: ACCUMULATOR+1. После выхода из цикла содержимое регистра ACCUMULATOR (старший байт суммы) заносится в EEPROM по адресу h’00’ с использованием подпрограммы EE_PUT.
Программа 15.12. Определение базового напряжения
; *******************
; * ФУНКЦИЯ: Суммирует 256 выборок аналогового сигнала для *
; * ФУНКЦИЯ: нахождения среднего значения, являющегося *
; * ФУНКЦИЯ: базовым напряжением, которое запоминается в *
; * ФУНКЦИЯ: модуле EEPROM *
; * РЕСУРСЫ: Подпрограммы GET_ANALOG, EE_PUT *
; * ВХОД: Нет *
; * ВЫХОД: Среднее значение 0-го канала — в EEPROM (h’00’) *
; *******************
LEARN clrf BASE; Обнуляем старший байт суммы
clrf BASE+1; Обнуляем младший байт суммы
clrf COUNT; Обнуляем счетчик цикла
LEARN_LOOP
clrw; Работаем с 0-м аналоговым каналом
call GET_ANALOG; Оцифровываем
addwf BASE+1,f; Прибавляем к младшему байту суммы
btfsc STATUS,С; Был перенос?
incf BASE,f; ЕСЛИ да, TO инкрементируем старший байт
decfsz COUNT,f; Уменьшаем счетчик цикла на единицу
goto LEARN_LOOP
; Запоминаем среднее в EEPROM
movf BASE,w; Берем среднее значение
bsf STATUS,RP1; Переключаемся во 2-й банк
clrf EEADR; Будем писать по адресу h’00’
movwf EEDATA; Загружаем байт данных
call EE_PUT; Запоминаем его
return ; Все сделали
В реальной ситуации лучшего результата можно достичь, беря 65 536 отсчетов и накапливая их в 3-байтной сумме. И опять же, старший байт этой суммы будет представлять собой усредненное значение.
Вопросы для самопроверки
15.1. В соответствии с хорошим стилем программирования данные, записываемые в EEPROM, следует верифицировать. Как можно модифицировать подпрограмму EE_PUT из Программы 15.2, чтобы она возвращала в регистре ERROR число —1 в случае, если запись прошла неудачно? В противном случае в этом регистре должен быть ноль.
15.2. В Программе 15.5 мы поместили таблицу преобразования в память программ, выровняв ее для упрощения вычисления 1-байтного индекса по 256-байтной границе (а именно h’300’). В результате, чтобы обратиться к элементу nn таблицы, нам достаточно поместить адрес h’3nn’ в регистры EEADRH: EEADR.
Размещение сегментов программы по адресам, заданным пользователем, является не очень хорошей идеей, поскольку при последующих модификациях программы может произойти наложение участков кода. Более надежным будет оставить размещение меток на совести ассемблера. Однако в нашем случае необходимо прибавлять значение nn к адресу, по которому ассемблер разместил таблицу TABLE. К сожалению, адрес памяти программ 13-битный, а микроконтроллеры PIC выполняют арифметические операции только с 8-битными числами. В Micmchip-совместимых ассемблерах предусмотрены директивы high и low, с помощью которых можно обратиться соответственно к старшему и младшему байту адреса метки, например movlw low TABLE. Используя эти директивы, доработайте подпрограмму SQUARE, чтобы она могла работать при отсутствии в программе директивы org h’300’.
15.3. В Microchip-совместимых ассемблерах имеется директива da, которая может использоваться для описания строк символов в памяти программ, например:
MESSAGE da "Hello world ",0
Эта директива помещает символы, расположенные между кавычками, в память программ, причем в каждое 14-битное слово заносится по два символа, представленных 7-битным кодом ASCII. Завершается строка словом с нулевым значением. Служебный символ п означает «новая строка», его ASCII-код равен h’0A’.
Полагая, что мы работаем с PIC16F87X, напишите подпрограмму, называемую PDATA, для выборки каждого символа из памяти программ и передачи его на терминал с помощью подпрограммы PUTCHAR из Программы 12.14 (стр. 421).
15.4. В некоторых системах безопасности гостиниц для электронных замков номеров используются перепрограммируемые смарт-карты на базе микроконтроллеров PIC. При регистрации в гостинице в эту карту заносятся следующие данные:
1. 4-разрядный номер комнаты, например 1311.
2. Дата начала срока действия ключа, например 13072005.
3. Дата окончания срока действия ключа, например 15072005.
Предположим, что микроконтроллер смарт-карты имеет встроенный модуль EEPROM, а для обмена информацией с терминалом на ресепшине используется подпрограмма обмена по последовательному порту, аналогичная реализованной в Программе 12.14 на стр. 421. Данные передаются в указанном порядке в кодировке ASCII, причем перед началом пакета передается символ STX, после завершения пакета — ЕТХ, а сами данные внутри пакета разделяются символом SP (см. Табл. 1.1 на стр. 18). Напишите подпрограмму, выполняющую разбор принимаемых данных и записывающую их в EEPROM.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОК