Использование EEPROM в Xmega


    Данная статья посвящена особенностям использования энергонезависимой памяти EEPROM в микропроцессорах Xmega.
    EEPROM (Electrically Erasable Programmable Read-Only Memory) — электрически стираемое перепрограммируемое постоянное запоминающее устройство (ЭСППЗУ), один из видов энергонезависимой памяти (таких как PROM и EPROM). Память такого типа может стираться и заполняться данными до миллиона раз.
    Микропроцессоры Xmega имеют энергонезависимую память EEPROM, адреса и доступ к которой могут располагаться как в отдельном пространстве данных (по умолчанию), так и в стандартном пространстве данных. Память EEPROM поддерживает как доступ к отдельному байту, так и к странице.
    Расположение памяти EEPROM в пространстве данных процессора показано на следующем рисунке:
    Контроллер энергонезависимой памяти (NVM)
    Доступ к памяти EEPROM осуществляется через контроллер энергонезависимой памяти, который также используется для доступа к fuse-битам и битам блокировки (lock), а также для программного обновления Flash-памяти.
    Поскольку контроллер NVM используется не только для обслуживания памяти EEPROM, необходимо проверять, не занять ли контроллер NVM другими операциями перед использованием его для доступа к EEPROM. Бит занятости контроллера NVM (NVMBUSY) в регистре состояния NVM (STATUS) установлен всегда когда контроллер занят.
    Используя контроллера NVM для доступа к памяти EEPROM осуществляется через использование команд NVM. Существуют команды для чтения, записи, стирания и т.д. Процедура запуска команды осуществляется следующим образом:
    - Подождите окончания предыдущих операций NVM контроллера.
    - Загрузите необходимую информацию в адресные регистры (ADDRn) и/или в регистры данных (DATAn).
    - Загрузите код команды в регистр команды (CMD).
    - Загрузите сигнатуру Защитного регистра ввода-вывода Protect IO Register (значение 0xD8) в регистр изменения конфигурации защитыConfiguration Change Protection (CCP). Это автоматически отключит все прерывания на следующие 4 цикла работы центрального процессора.
    - В течение следующих 4 циклов работы центрального процессора установить бит выполнения команды (CMDEX) в регистре команд.
    - Работа завершится по очищению бита занятости NVM (NVMBUSY).

    Команды контроллера NVM для работы с EEPROM показаны в следующей таблице:
    Установка бита разрешения отображения EEPROM (EEMAPEN) в регистре управления NVM Control Register B (CTRLB) позволяет памяти EEPROM отображаться в пространстве памяти данных вместо использования контроллера NVM. Однако, также необходима проверка занятости NVM перед получением доступа к EEPROM, поскольку NVM используется внутренними командами.
    Очищение и заспись
    Существуют два способа обновления памяти EEPROM: неделимая запись (очищение с последующей записью) и отдельная операция. При неделимой записи, необходимая часть EEPROM очищается или записывается одной операцией, а при отдельной - командой записи без очищения.
    При очищении в пространстве EEPROM все биты устанавливаются в логическую единицу. Отдельная запись может программировать выбранные биты в логический ноль. Операции отдельной записи не могут изменить бит с нуля на единицу, в противоположность неделимой записи, которая вначале стирает память в логические единицы, а затем записывает логические нули в выбранные биты. Это означает, что многократные записи с различными значениями, в конечном счете, приведет к тому, что вся память будет состоять из логических нулей, если в промежутке между записями память не будет стираться. Это схоже с логической операцией «И» между входным и выходным значениями.
    Неделимая запись бита занимает примерно двойное время по сравнению с одиночной записью или стиранием. Поэтому отдельная операция может быть использована для сокращения времени очищения EEPROM, например, во время инициализации. Допустим, если приложение должно хранить важные данные, то при определении пропадания питания отдельная запись займет меньше времени, чем непрерывная.
    Другим полезным качеством отдельной записи является увеличение срока службы EEPROM, что является особенно полезным для приложений с частыми обновлениями EEPROM. Пишите программу так, чтобы память EEPROM не очищалась, если это делать не обязательно, для увеличения ее срока службы.
     Различные операции записи и стирания, вместе со значениями данных до и после выполнения процедур показаны на следующем рисунке:
    Память EEPROM организована в страничном варианте и операции записи и стирания также поддерживают страничный вариант. Размер страницы зависит от размера памяти, информацию о которой можно узнать в описании на процессор. Операции записи и стирания используют временный буфер страницы для отслеживания байтов, к которым необходимо получить доступ и для непосредственной записи.
    Операция записи фактически состоит из двух операций: загрузки страницы буфера с данными и записи данных в страницу EEPROM. При загрузке страницы буфера младшая часть адреса EEPROM используется для выбора байта в странице буфера, в то время как старшая часть игнорируется. При записи и очищении страницы старшая часть адреса выбирает страницу, в то время как младшая часть игнорируется.
    На следующем рисунке показан пример, где один байт загружен в буфер страницы с последующей записью в страницу EEPROM. Рисунок показывает, что адрес буфера, которое было загружено отмечается, поэтому страница буфера знает какой байт нуждается в записи.
    При операции стирания страницы из EEPROM будут стерты только те байты, что отмечены в буфере. Если Вы не планируете выполнить последующую запись, то фактические значения в буфере не важно.
    Как только байт был загружен в буфер, бит активации загрузки в страницу буфера EEPROM (EELOAD) в регистре состояния NVM (STATUS) будет установлен. Бит остается установленным, пока или буфер не будет сброшен или не произойдет запись страницы (непрерывная или разделенная). Для очищения буфера страницы дайте команду контроллеру NVM очищения буфера страницы EEPROM (значение байта 0x36).
    Отметим, что если происходит многократная загрузка байтов в одно и тоже место буфера, то конечное значение будет результатом логической операции «И» между предыдущим значением и вновь записываемым, подобными тому, что происходит при операции записи страницы.
    Сопряжение с DMA
    При использовании в одном приложении DMA и EEPROM нужно учитывать, что DMA контроллер не может получить доступ к EEPROM если центральный процессор находится в любом из спящих режимов. Процесс обмена с DMA будет прекращен если доступ будет инициирован в спящем режиме.
    Сопряжение с прерываниями
    При использовании в одном приложении прерываний и EEPROM необходимо придерживаться следующих правил:
    Не портите содержание буфера страницы EEPROM. При загрузке страницы буфера убедитесь, что процедуры обработки прерываний не имеют доступ к странице буфера. Если же процедура обработки прерываний используется для получения доступа к странице буфера, убедитесь, что другие части приложения не имеет одновременного доступа к ней.
    Вместо непрерывного опроса флага занятости NVM контроллера (NVMBUSY) для определения окончания работы NVM возможно разрешить прерывание по EEPROM настроенное на соответствующий уровень прерываний в битовом поле (EELVL) в регистре управления прерываниями EEPROM (INTCTRL). Соответствующая процедура обработки прерываний будет вызываться всякий раз, когда флага занятости NVM контроллера (NVMBUSY) будет не установлен. Это может быть полезно, например, для осуществления обновления EEPROM управляемое по прерываниям.
    Рекомендации по применению
    Далее будет предложены рекомендуемые процедуры по конфигурации и запуску EEPROM.
    Доступ к карте портов ввода-ввода
    Доступ к карте портов ввода-вывода означает, что доступ ко всей EEPROM осуществляется с использованием регистров ввода-вывода в контроллере NVM и запускается командами NVM.
    Чтение EEPROM
    Для определения адреса EEPROM необходимо выполнить следующие:
     - Дождаться окончания работы NVM контроллера.
    - Загрузить регистры адреса NVM (ADDRn) с требуемым байтом адреса EEPROM.
    - Запустить команду чтения EEPROM (значение 0x06) в регистре команд NVM контроллера (CMD).
    - Центральный процессор будет остановлен на два цикла системных часов, в это время данные доступны в регистре данных NVM (DATA0). Ждать очистки бита занятости NVM не нужно.
    Эта операция не поддерживается, если разрешен доступ к отображаемой памяти EEPROM.
    Загрузка страницы буфера EEPROM
    Для загрузки байта во временную страницу буфера необходимо выполнить следующие:
    - Дождаться окончания работы NVM контроллера.
    - Запустить команду загрузки страницы буфера EEPROM (значение 0x33) в регистре команд NVM контроллера (CMD).
    - Загрузить регистры адреса NVM (ADDRn) с требуемым байтом адреса EEPROM.
    - Загрузить регистр данных NVM (DATA0) с байтом данных. Это автоматически вызовет процедуру загрузки буфера. Эта команда не требует установки бита выполнения команды (CMDEX) в регистре команд NVM (CTRLA). В течение выполнения этой операции центральный процессор будет остановлен на один цикл.
    Важно чтобы запись данных в регистр данных выполнялось в последнюю очередь, так как она вызывает загрузку буфера. Порядок выполнения других шагов не важен.
    Эта операция не поддерживается, если разрешен доступ к отображаемой памяти EEPROM.
    Неделимая запись (запись и очищение) страницы EEPROM
    Для очищения страницы и записи подготовленной страницы буфера данных в EEPROM необходимо выполнить следующие:
    - Дождаться окончания работы NVM контроллера.
    - Загрузить регистры адреса NVM (ADDRn) с адресом EEPROM в пределах страницы, которая будет обновлена.
    - Запустить команду неделимой записи EEPROM (значение 0x35) в регистре команд NVM контроллера (CMD).
    - Операция закончена, когда бит занятости NVM очищен.
    - На странице EEPROM будет обновлен только адрес буфера, который был загружен.
    Очищение страницы EEPROM
    - Для очищения страницы буфера EEPROM без выполнения записи необходимо выполнить следующие:
    - Дождаться окончания работы NVM контроллера.
    - Загрузить регистры адреса NVM (ADDRn) с адресом EEPROM в пределах страницы, которая будет стерта.
    - Запустить команду стирания страницы EEPROM (значение 0x32) в регистре команд NVM контроллера (CMD).
    - Операция закончена, когда бит занятости NVM очищен.
    - На странице EEPROM будет очищен только адрес буфера, который был загружен. Поэтому, указывающие байты должны быть загружены в каждое буферное пространство соответственно байтам, которые Вы хотите стереть из страницы EEPROM.
    Отдельная запись (только запись) страницы EEPROM
    - Для записи подготовленной страницы буфера EEPROM в уже стертую страницу необходимо выполнить следующие:
    - Дождаться окончания работы NVM контроллера.
    - Загрузить регистры адреса NVM (ADDRn) с адресом EEPROM в пределах страницы, которая будет обновлена.
    - Запустить команду отдельной записи страницы EEPROM (значение 0x34) в регистре команд NVM контроллера (CMD).
    - Операция закончена, когда бит занятости NVM очищен.
    - На странице EEPROM будет обновлен только адрес буфера, который был загружен.
    - Доступ к отображаемой памяти
    - Доступ к отображаемой памяти означает, что процедуры чтения EEPROM и загрузки страниц буфера осуществляются в пространстве данных. Это означает, что данные EEPROM могут быть прочитаны простым чтением адреса в памяти данных. Для семейства XMEGA A1 отображаемая память EEPROM начинается с адреса 0x1000.
    Загрузка страницы буфера осуществляется так же просто, как и запись в память данных. Однако сброс буфера, очищение и запись страниц могут быть выполняются с использованием NVM контроллера, также как в карте портов ввода-ввода. Кроме того, контроллер NVM не должен быть занят при получении доступа к EEPROM.
    Для неделимой записи при использовании отображаемой памяти необходимо выполнить следующие:
     - Дождаться окончания работы NVM контроллера.
    - Загрузить страницу буфера записью непосредственно в пространство данных пока еще находящуюся одной страницей EEPROM.
     - Загрузить регистры адреса NVM (ADDRn) с адресом EEPROM в пределах страницы, которая будет обновлена.
    Запустить команду неделимой записи страницы EEPROM (значение 0x35) в регистре команд NVM контроллера (CMD).
    Операция закончена, когда бит занятости NVM очищен.
    Процедуры для стирания и отдельной записи выполняются аналогично. Короче говоря, чтение данных и загрузка страницы буфера заменены доступом к отображаемой памяти, остальное схоже с процедурами при доступе к карте портов ввода-вывода.
    Ну и как обычно, напоследок рабочий пример программы.
    Пример.
     Задача: Написать программу подстройки АЦП. Суть, которой заключается в активизации процедуры определения смещения при нажатии на кнопку (появлении логической 1 на определенной ножке) и запись полученного значения смещения в EEPROM. Результирующий сигнал АЦП вычисляется путем вычитания сохраненного в EEPROM смещения из фактически полученного с АЦП. Полученное смещение будем выдавать на ЦАП, чтобы видеть, как оно изменяется, но тут надо учесть, что выдаваемая величина на ЦАП может быть отлична от реального смещения АЦП, т.к. ЦАП иметь свое смещение.
   Будем использовать внешний кварцевый генератор с частотой 16 МГц и основной системной частоты 48 МГц и нормальный режим таймера. Будем использовать ножку 2 порта K в качестве входа для запуска режима вычисления смещения (на нее, например, можно повесить кнопку), причем она будет подтянута к питанию. Ножку 0 порта B будем использовать в качестве индикатора вычисления смещения – выход равен логической единице в режиме вычисления смещения и нулю при выходе из этого режима (на данную ножку можно повесить светодиод). Адрес хранения слова смещения в EEPROM определим равным 0. Смещение будем определять средним значением за 1000 опросов. Учтите, что для корректного вычисления смещения входной сигнал на канале АЦП в процессе его вычисления должен быть постоянным. В качестве опрашиваемого канала АЦП возьмем ADCA.CH0, это ножка 3 порта A. АЦП настроим на беззнаковый автоматический режим. В качестве выходного канала ЦАП возьмем канал 0 DACB. Код работающей программы для процессора Xmega128A1 имеет следующий вид:
Код работающей программы для процессора Xmega128A1 имеет следующий вид: 

#define ENABLE_BIT_DEFINITIONS // разрешение использования групповых битовых имен 
/* Объявление используемых библиотек */
#include <ioxm128a1.h>
#include <ina90.h>
#include <stdint.h>
#define EEPROM_ADDR_A 0x00 // Установка адреса слова смещения АЦП в EEPROM
#define EEPROM_PAGE_ADDR 0 // Установка страницы адреса слова смещения АЦП в EEPROM
#define EEPROM_PAGESIZE 32 // Установка размера страницы EEPROM
/* Установка бита CMDEX */
#define NVM_EXEC()
asm("push r30" "\n\t" \
"push r31" "\n\t" \
"push r16" "\n\t" \
"push r18" "\n\t" \
"ldi r30, 0xCB" "\n\t" \
"ldi r31, 0x01" "\n\t" \
"ldi r16, 0xD8" "\n\t" \
"ldi r18, 0x01" "\n\t" \
"out 0x34, r16" "\n\t" \
"st Z, r18" "\n\t" \
"pop r18" "\n\t" \
"pop r16" "\n\t" \
"pop r31" "\n\t" \
"pop r30" "\n\t" \
)
long int DataA; // Данные с канала АЦП
int CountSmesh=1000; // Установка счетчика вычисления смещения
int SmeshA; // Смещение канала АЦП
char StartTest=0; // Флаг начала тестового режима вычисления смещения
char EndTest=0; // Флаг окончания тестового режима вычисления смещения
void InitADC(void) // Настройка АЦП
{
/* беззнаковый режим, автоматический режим, 12-битный результат с правым выравниванием */
ADCA.CTRLB = ADC_RESOLUTION_12BIT_gc | 0x08;
/* разрешение работы бэндгап-элемента, внутреннее опорное напряжение 1В */
ADCA.REFCTRL = ADC_REFSEL_INT1V_gc | 0x02;
/* периферийная частота = clk/16 (48MHz/16)*/
ADCA.PRESCALER = ADC_PRESCALER_DIV16_gc;
/* канал 0 ADCA настроен на внешний несимметричных вход */
ADCA.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
/* Ножка 3 порта А настроена как положительный вход */
ADCA.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN3_gc;
/* Разрешение работы АЦП */
ADCA.CTRLA|=ADC_ENABLE_bm;
}
void InitDAC(void) // Настройка ЦАП
{
DACB.CTRLB = 0; // одноканальная работа канала 0
DACB.CTRLA = 0x05; // Разрешение работы DACB и канала 0
DACB.TIMCTRL = 0x50; // минимум 48 CLK между преобразованиями
}
int main(void)
{
__disable_interrupt(); // Отключение всех прерываний
/* Установка тактирования от 3-х кранной частоты внешнего генератора 3*16=48 */
OSC.XOSCCTRL = 0xCB; // выбор внешнего генератора с временем запуска 16 тыс. CLK и частотой 12-16 МГц
OSC.CTRL = 0x08; // разрешение работы внешнего генератора
while((OSC.STATUS & 0x08) == 0 ) ; // ожидание появления в регистре статуса бита включения синхронизации от внешнего генератора
OSC.PLLCTRL = 0xC3; // настройка блока PLL на синхронизацию от внешнего источника и 3-х кратоное умножение
OSC.CTRL = OSC.CTRL | 0x10; // разрешение работы блока PLL
while((OSC.STATUS & 0x10) == 0 ) ; // ожидание появления в регистре статуса бита включения блока PLL
CCP = 0xD8; // включение защиты от изменения регистров ввода-вывода на время изменения синхронизации
CLK.CTRL = 0x04; // настройка системной синхронизации от блока PLL
OSC.CTRL = OSC.CTRL & 0xFE; // отключение системной синхронизации от внутреннего RC-генератора частотой 2 МГц
PORTB.DIRSET = 0x01; // Настройка ножки 0 порта B как выхода
PORTK.PIN2CTRL = PORT_OPC_PULLUP_gc; // Установка подтянутого входа порта K ножки 2
TCC0.CTRLA=0x05; // N=64
TCC0.PER=50-1; // частота таймера 15кГц при системной частоте 48МГц
TCC0.INTCTRLA=1; // частота таймера 15кГц при системной частоте 48МГц
PMIC.CTRL = 1; // приоритет прерываний уровня low
InitADC(); // Запуск процедуры инициализации АЦП
InitDAC(); // Запуск процедуры инициализации ЦАП
uint16_t address; // объявление временной переменной адреса
/* Чтение первого байта слова из EEPROM*/
do {} while ((NVM.STATUS & NVM_NVMBUSY_bm) == NVM_NVMBUSY_bm); // Ожидание освобождения NVM
address = (uint16_t)(EEPROM_PAGE_ADDR*EEPROM_PAGESIZE)|(EEPROM_ADDR_A & (EEPROM_PAGESIZE-1)); // Определение адреса байта в EEPROM
NVM.ADDR0 = address & 0xFF; // Установка адреса байта в EEPROM
NVM.ADDR1 = (address >> 8) & 0x1F;
NVM.ADDR2 = 0x00;
NVM.CMD = NVM_CMD_READ_EEPROM_gc; // Установка команды чтения байта EEPROM
NVM_EXEC(); // Установка бита CMDEX
SmeshA=NVM.DATA0; // Сохранение первого байта смещения из EEPROM
/* Чтение второго байта слова из EEPROM*/
do {} while ((NVM.STATUS & NVM_NVMBUSY_bm) == NVM_NVMBUSY_bm); // Ожидание освобождения NVM
address = (uint16_t)(EEPROM_PAGE_ADDR*EEPROM_PAGESIZE)|((EEPROM_ADDR_A+1) & (EEPROM_PAGESIZE-1)); // Определение адреса байта в EEPROM
NVM.ADDR0 = nbsp; address & 0xFF; // Установка адреса байта в EEPROM
NVM.ADDR1 = (address >> 8) & 0x1F;
NVM.ADDR2 = 0x00;
NVM.CMD = NVM_CMD_READ_EEPROM_gc; // Установка команды чтения байта EEPROM
NVM_EXEC(); // Установка бита CMDEX
SmeshA|=NVM.DATA0<<8; // Сохранение второго байта смещения из EEPROM
__enable_interrupt(); // Разрешение прерываний
while(1){} // пустой бесконечный цикл
}
#pragma vector=TCC0_OVF_vect // обработка прерываний по переполнению таймера С0
__interrupt void irqTCC1_OVF_vect(void)
{
uint16_t address; // объявление временной переменной адреса
if (!(PORTK.IN & 0x04)) // Если выбран режим установки смещения
{
if (CountSmesh) // Если счетчик вычисления смещения не равен нулю, то
{
PORTK.OUTSET = 0x10; // Установка бита индикации определения смещения
CountSmesh--; // Уменьшение счетчика вычисления смещения
if (!StartTest) // Если флаг начала тестового режима вычисления смещения не установлен
{
DataA=0; // Обнуление данных с канала АЦП
StartTest=1; // Установка флага начала тестового режима вычисления смещения
}
DataA=DataA+ADCA.CH0RES;// Интегрирование данных с канала АЦП
}
else // Если не выбран режим установки смещения
{
if (!EndTest) // Если флаг окончания тестового режима вычисления смещения не установлен
{
EndTest=1; // Установка флага окончания тестового режима вычисления смещения
SmeshA=2047-DataA/1000;// Вычисление среднего смещения за 1000 опросов
/* Загрузка страницы из EEPROM*/
do {} while ((NVM.STATUS & NVM_NVMBUSY_bm) == NVM_NVMBUSY_bm); // Ожидание освобождения NVM
NVM.CMD = NVM_CMD_LOAD_EEPROM_BUFFER_gc; // Установка команды загрузки страницы EEPROM
NVM.ADDR1 = 0x00;
NVM.ADDR2 = 0x00;
NVM.ADDR0=EEPROM_ADDR_A; // запись адреса первого байта EEPROM
NVM.DATA0= SmeshA& 0xFF; // Запись в страницу первого байта смещения
NVM.ADDR0=EEPROM_ADDR_A+1; // запись адреса второго байта EEPROM
NVM.DATA0 = SmeshA >> 8; // Запись в страницу второго байта смещения
do {} while ((NVM.STATUS & NVM_NVMBUSY_bm) == NVM_NVMBUSY_bm); // Ожидание освобождения NVM
address = (uint16_t)(EEPROM_PAGE_ADDR*EEPROM_PAGESIZE); // Определение адреса страницы в EEPROM
NVM.ADDR0 = address & 0xFF; // Установка адресаов записываемых байтов в EEPROM
NVM.ADDR1 = (address >> 8) & 0x1F;
NVM.ADDR2 = 0x00;
NVM.CMD = NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc; // Установка команды неделимой записи страницы EEPROM
NVM_EXEC(); // Установка бита CMDEX
PORTK.OUTCLR = 0x10; // Сброс бита индикации определения смещения
}
}
}
else // Если не выбран режим установки смещения
{
StartTest=0; // Очищение флага начала тестового режима вычисления смещения
EndTest=0; // Очищение флага окончания тестового режима вычисления смещения
CountSmesh=1000; // Установка счетчика вычисления смещения
ADCA.CTRLA |= 0x04; // запуск преобразования канала 0
while(!ADCA.CH0.INTFLAGS); // ожидание установки флага конца прерываний канала 0
ADCA.CH0.INTFLAGS=ADC_CH_CHIF_bm; // очищение флага
DataA = ADCA.CH0RES+SmeshA; // Сохранение данных с учетом смещения
if (DACB.STATUS & 1) // проверка пустоты регистра канала 0
DACB.CH0DATA = DataA; // если пуст - запись в ЦАП данных полученных с АЦП
}
}
    Описанный выше код, полностью корректен и, наверное, более правилен с точки зрения опроса освобождения контроллера NVM, однако ее можно упростить использованием объявлением регистра находящегося в EEPROM с помощью __eeprom. Что иллюстрирует приведенный ниже код. Он также полностью работоспособен и как Я его не мучил, некорректных записей и чтений EEPROM не давал, возможно, они возникнут при существенном усложнении программы прерываниями и расширенным использованием периферии. Информации о данном способе записи и чтения EEPROM на XMEGA у разработчиков я не нашел.
#define ENABLE_BIT_DEFINITIONS // разрешение использования групповых битовых имен 
/* Объявление используемых библиотек */
#include <ioxm128a1.h>
#include <ina90.h>
#include <stdint.h>
long int DataA; // Данные с канала АЦП
int CountSmesh=1000; // Установка счетчика вычисления смещения
int SmeshA; // Смещение канала АЦП
char StartTest=0; // Флаг начала тестового режима вычисления смещения
char EndTest=0; // Флаг окончания тестового режима вычисления смещения
__eeprom int SmeshAe; // переменная смещения хранящаяся в EEPROM
void InitADC(void) // Настройка АЦП
{
/* беззнаковый режим, автоматический режим, 12-битный результат с правым выравниванием */
ADCA.CTRLB = ADC_RESOLUTION_12BIT_gc | 0x08;
/* разрешение работы бэндгап-элемента, внутреннее опорное напряжение 1В */
ADCA.REFCTRL = ADC_REFSEL_INT1V_gc | 0x02;
/* периферийная частота = clk/16 (48MHz/16)*/
ADCA.PRESCALER = ADC_PRESCALER_DIV16_gc;
/* канал 0 ADCA настроен на внешний несимметричных вход */
ADCA.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
/* Ножка 3 порта А настроена как положительный вход */
ADCA.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN3_gc;
/* Разрешение работы АЦП */
ADCA.CTRLA|=ADC_ENABLE_bm;
}
void InitDAC(void) // Настройка ЦАП
{
DACB.CTRLB = 0; // одноканальная работа канала 0
DACB.CTRLA = 0x05; // Разрешение работы DACB и канала 0
DACB.TIMCTRL = 0x50; // минимум 48 CLK между преобразованиями
}
int main(void)
{
__disable_interrupt(); // Отключение всех прерываний
/* Установка тактирования от 3-х кранной частоты внешнего генератора 3*16=48 */
OSC.XOSCCTRL = 0xCB; // выбор внешнего генератора с временем запуска 16 тыс. CLK и частотой 12-16 МГц
OSC.CTRL = 0x08; // разрешение работы внешнего генератора
while((OSC.STATUS & 0x08) == 0 ) ; // ожидание появления в регистре статуса бита включения синхронизации от внешнего генератора
OSC.PLLCTRL = 0xC3; // настройка блока PLL на синхронизацию от внешнего источника и 3-х кратоное умножение
OSC.CTRL = OSC.CTRL | 0x10; // разрешение работы блока PLL
while((OSC.STATUS & 0x10) == 0 ) ; // ожидание появления в регистре статуса бита включения блока PLL
CCP = 0xD8; // включение защиты от изменения регистров ввода-вывода на время изменения синхронизации
CLK.CTRL = 0x04; // настройка системной синхронизации от блока PLL
OSC.CTRL = OSC.CTRL & 0xFE; // отключение системной синхронизации от внутреннего RC-генератора частотой 2 МГц
PORTB.DIRSET = 0x01; // Настройка ножки 0 порта B как выхода
PORTK.PIN2CTRL = PORT_OPC_PULLUP_gc; // Установка подтянутого входа порта K ножки 2
TCC0.CTRLA=0x05; // N=64
TCC0.PER=50-1; // частота таймера 15кГц при системной частоте 48МГц
TCC0.INTCTRLA=1; // частота таймера 15кГц при системной частоте 48МГц
PMIC.CTRL = 1; // приоритет прерываний уровня low
InitADC(); // Запуск процедуры инициализации АЦП
InitDAC(); // Запуск процедуры инициализации ЦАП
SmeshA=SmeshAe; // Чтение значения смещения из EEPROM
__enable_interrupt(); // Разрешение прерываний
while(1){} // пустой бесконечный цикл
}
#pragma vector=TCC0_OVF_vect // обработка прерываний по переполнению таймера С0
__interrupt void irqTCC1_OVF_vect(void)
{
if (!(PORTK.IN & 0x04)) // Если выбран режим установки смещения
{
if (CountSmesh) // Если счетчик вычисления смещения не равен нулю, то
{
PORTK.OUTSET = 0x10; // Установка бита индикации определения смещения
CountSmesh--; // Уменьшение счетчика вычисления смещения
if (!StartTest) // Если флаг начала тестового режима вычисления смещения не установлен
{
DataA=0; // Обнуление данных с канала АЦП
StartTest=1; // Установка флага начала тестового режима вычисления смещения
}
DataA=DataA+ADCA.CH0RES;// Интегрирование данных с канала АЦП
}
else // Если не выбран режим установки смещения
{
if (!EndTest) // Если флаг окончания тестового режима вычисления смещения не установлен
{
EndTest=1; // Установка флага окончания тестового режима вычисления смещения
SmeshA=2047-DataA/1000; // Вычисление среднего смещения за 1000 опросов
SmeshAe=SmeshA; // Сохранение в EEPROM значения смещения
PORTK.OUTCLR = 0x10; // Сброс бита индикации определения смещения
}
}
}
else // Если не выбран режим установки смещения
{
StartTest=0; // Очищение флага начала тестового режима вычисления смещения
EndTest=0; // Очищение флага окончания тестового режима вычисления смещения
CountSmesh=1000; // Установка счетчика вычисления смещения
ADCA.CTRLA |= 0x04; // запуск преобразования канала 0
while(!ADCA.CH0.INTFLAGS); // ожидание установки флага конца прерываний канала 0
ADCA.CH0.INTFLAGS=ADC_CH_CHIF_bm; // очищение флага
DataA = ADCA.CH0RES+SmeshA; // Сохранение данных с учетом смещения
if (DACB.STATUS & 1) // проверка пустоты регистра канала 0
DACB.CH0DATA = DataA; // если пуст - запись в ЦАП данных полученных с АЦП
}
}
    Blogger Comment
    Facebook Comment

0 коммент.:

Отправить комментарий