В предыдущей статье мы остановились на использовании
таймера для отсчета временных задержек, далее мы рассмотрим, как можно
реализовать задержки с помощью прерываний, что позволит программе выполнять
полезную работу вместо ожидания окончания счета. Контроллер прерываний (ITC) имеет 32 вектора
прерывания от периферийных устройств. Каждому вектору может быть задан уровень
приоритета от 0 (низший) до 3 (высший). По умолчанию всем векторам присвоен
уровень 0. В нашем примере изменять уровень приоритета не нужно, поэтому
дополнительная инициализация ITC не нужна.
Для отсчета задержки будем использовать уже знакомый нам
8-разрядный таймер TIM4.
Таймер может вырабатывать прерывания по переполнению счетчика. Прерывание
должно вырабатываться циклически, поэтому будем использовать таймер в режиме
циклического счета с автозагрузкой при переполнении. Данный режим отличается
тем, что запись начального значения счетчика производится один раз в регистр
автозагрузки TIM4_ARR, причем записывается в
него не само начальное значение счетчика, а число импульсов до сброса в 0, т.е.
формула приобретает вид:
X = Fclk / (2n / T),
где Fclk – частота на выходе
блока CLK, n – значение
регистра предделителя (для TIM4
может быть от 0 до 7), T – время задержки.
Данное
представление более наглядно (фактически мы записываем коэффициент деления
входной частоты), однако в документации на таймер эта особенность не описана,
что поначалу ввело меня в заблуждение, разобраться удалось только после изучения
исходного текста библиотеки сенсорных кнопок от ST Microelectronics.
Запросы на
прерывание разрешаются установкой бита UIE в регистре TIM4_IER, для того, чтобы не произошло неожиданного прерывания сразу
после его разрешения, сбросим флаг генерации прерывания UIF в регистре TIM4_SR.
Таким
образом, инициализация таймера примет вид:
//Инициализируем TIM4
TIM4_SR_bit.UIF=0; //Сбросим признак прерывания
TIM4_PSCR=0x07; //Предделитель на 2^7=128 16000000/128 = 125000 Hz
TIM4_ARR=250; //Регистр автозагрузки таймера при
переполнении 125000 / 250 = 500 Hz
TIM4_IER_bit.UIE=1; //Разрешаем генерацию прерывания при
переполнеини
TIM4_CR1_bit.CEN=1; //Разрешаем счет
Введем в текст программы, перед функцией main() обработчик прерывания таймера.
Те, кто знаком со средой IAR для AVR могут заметить, что синтаксис описания векторов совпадает,
за исключением имен векторов. Вектора для нашего МК описаны в файле iostm8s105s6.h.
#include
"intrinsics.h" //Здесь описана функция __enable_interrupt()
char
Timer1;
#pragma
vector=TIM4_OVR_UIF_vector
__interrupt
void TIM4_OVR_UIF(void)
{
TIM4_SR_bit.UIF=0; //Сбросим признак прерывания
if(Timer1) Timer1--; //Программный таймер
}
Первое, что
необходимо сделать в любом обработчике прерывания – это сбросить флаг
прерывания на устройстве, которое вызвало прерывание. В противном случае, при
выходе из обработчика прерывания мы вновь в него попадем. Далее, мы можем выполнить
необходимые действия по обслуживанию прерывания – в нашем случае
декрементировать программный счетчик.
Основной цикл
функции main() запишем
следующим образом:
__enable_interrupt();
//Разрешаем прерывания
//Собственно мигание
Timer1=250; //500 / 250 = 2 Hz (500 мс)
while(1)
{
if(!Timer1) //Прошло 500 мс?
{
Timer1=250; //500 / 250 = 2 Hz (500 мс)
PD_ODR_bit.ODR0=~PD_ODR_bit.ODR0; //Изменим состояние светодиода
}
//Здесь можно добавить другие действия
}
В строчке if(!Timer1) мы проверяем прошло ли заданное
нами число прерываний с момента присвоения начального значения переменной Timer1. Прерывания в нашем
примере вызываются с частотой 500Гц, значит, с учетом коэффициента деления Timer1 светодиод будет
изменять свое состояние раз в 500 мс, что дает нам частоту мигания 1 Гц.
Запишем получившуюся программу (проект LED-03) в МК и проверим результат
работы.
Данный пример
является простейшим генератором временной шкалы. Чтобы выполнять какие-либо действия
с определенной периодичностью необходимо добавить в обработчик прерывания
конструкции вида:
if(Timer2) Timer2--;
…
if(TimerN) TimerN--;
не забывая при этом объявить глобальные переменные TimerN. А в главный цикл
функции main() необходимо
ввести обработчики событий вида:
if(!Timer2)
{
Timer2=100; //500 / 100 = 5 Hz
(200мс)
//Выполним
какие-либо действия
}
…
if(!TimerN)
{
TimerN=50; //500 / 100 = 10 Hz (100 мс)
//Выполним
какие-либо действия
}
Количество обработчиков событий зависит от
числа используемых в программе периодических временных интервалов. Каждый
обработчик в общем случае является независимым от других. В последующих уроках
мы также будем использовать данные конструкции для выполнения периодических
действий.
Следующий пример демонстрирует использование возможностей
16-разрядного таймера для генерации сигналов с заданным периодом и
длительностью. На плате Discovery вывод PD0
имеет альтернативную функцию – выход канала сравнения таймера TIM3_CH2, что позволяет нам наглядно исследовать некоторые возможности
данного канала. За основу возьмем проект LED-01 из первой части статьи. Оставим в нем только инициализацию
CLK. Основной цикл
запишем просто в виде:
while(1);
т.е. программа, дойдя до этого места, просто зациклится. За
счет чего же будет изменяться состояние светодиода? Да за счет импульсов с
выхода канала сравнения таймера. Канал имеет несколько различных режимов, определяемых
битами OC2M регистра
TIM3_CCMR2. Возможны следующие значения:
0x00
– Заморожено – состояние выхода канала сравнения не меняет состояние вывода МК
0x01
– Активный уровень при совпадении – при совпадении значений регистров канала
сравнения и счетчика на выход передается 1
0x02
– Неактивный уровень при совпадении – при совпадении значений регистров канала
сравнения и счетчика на выход передается 0
0x03
– Изменить состояние выхода при совпадении.
0x04
– Установить выход в 0 постоянно
0x05
– Установить выход в 1 постоянно
0x06
– режим 1 ШИМ – выход в 1, пока TIM3_CNTR < TIM3_CCR2
0x07
– режим 2 ШИМ – выход в 0, пока TIM3_CNTR < TIM3_CCR2
Для решения нашей задачи мигания светодиодом, наиболее
подходящим является режим 0x03,
в этом случае, значение регистра TIM3_CCR2
может быть любым, но должно выполняться условие TIM3_CCR2 < TIM3_ARR. Универсальным решением
будет задание TIM3_CCR2=0, данное значение
записывается в регистр при сбросе МК. Для того, чтобы сигнал с выхода канала
сравнения CH2 попал на
физический вывод МК, необходимо установить бит CC2E в регистре TIM3_CCER1.
Таймер TIM3 является 16-битным и, аналогично TIM4 имеет те же основные функциональные
узлы - счетчик со схемой автозагрузки, регистры управления, состояния,
разрешения прерываний. Названия регистров и битов, выполняющих аналогичные
функции, совпадают. Различия заключаются в том, что регистры, связанные со
значением счетчика TIM3_CNTR, TIM3_ARR, TIM3_CCR1, TIM3_CCR2 представлены в виде пары 8-битных регистров xxxRH и
xxxRL каждый.
При записи в такие регистры всегда сначала записывается старший байт, затем
младший. Изменение значения 16-битного регистра происходит только после записи
младшего байта. Для расчета значения регистра TIM3_ARR можно использовать ту же формулу, что
и для TIM4, только
значение регистра предделителя n может находиться в диапазоне от 0 до 15, а диапазон выходных
значений X расширяется до 65535.
Теперь мы можем
записать в нашу программу перед вызовом бесконечного цикла код инициализации
для TIM3:
//Инициализируем
TIM3. Светодиод подключен на TIM3_CH2
TIM3_PSCR=0x08; //Предделитель 16000000 / 256 =
62500 Hz
TIM3_ARRH=0x7A; //Автозагрузка счетчика старший байт
31250 = 0x7A12
TIM3_ARRL=0x12; //Автозагрузка счетчика младший байт
TIM3_CCER1_bit.CC2E=1;
//Разрешаем выход CH2
TIM3_CCMR2_bit.OC2M=0x03; //Режим изменения состояния выхода при
совпадении
TIM3_CR1_bit.CEN=1;
//Разрешаем счет
Остается записать
нашу программу (проект LED-04)
в МК и увидеть тот же самый результат, что и в двух предыдущих примерах, однако
программа в это время выполняет пустой цикл, в который можно вписать любые
необходимые действия, в том числе, с использованием прерываний и временной шкалы.
Данный метод генерации
импульсов может использоваться для получения сигналов прямоугольной формы с
заданной частотой (например, звуковых). Включение и выключение сигнала может
производиться изменением бита CC2E регистра
TIM3_CCER1.
Следующий пример (проект
LED-05) иллюстрирует
использование канала сравнения в режиме ШИМ для управления яркостью свечения
светодиода. Он объединяет в себе два предыдущих. Таймер TIM4 используется для генерации
временной шкалы, TIM3 -
управляет светодиодом. Для перевода канала TIM3_CH2 в
режим ШИМ, запишем в поле OC2M регистра
TIM3_CCMR2 значение режима 0x06. Чтобы зависимость яркости свечения
от значения в регистре TIM3_CCR2 была прямой, включим
инверсию выхода (не забываем, что светодиод светится при низком уровне на
выходе) установкой бита CC2P в
регистре TIM3_CCER1. Несущая частота ШИМ
должна быть достаточно высокой (обычно более 30Гц), чтобы не наблюдалось
мерцание светодиода. При аппаратной генерации мы можем получить максимальную
частоту Fшим = 16000000
/ 65535 = 244Гц, что вполне достаточно
для нашего случая. Производитель также рекомендует в режиме ШИМ включать
буферизацию записи в регистры TIM3_ARR и
TIM3_CCR2, чтобы обновление значений
производилось автоматически в момент окончания счета. Для этого нужно установить
бит ARPE в регистре TIM3_CR1 и OC2PE в регистре TIM3_CCMR2.
Таким образом, код инициализации TIM3 примет вид:
//Инициализируем TIM3. Светодиод подключен на TIM3_CH2
CLK_PCKENR1|=0x40;
//Разрешаем подачу Clock на TIM3
TIM3_CR1_bit.ARPE=1; //Разрешаем буферизацию ARR
TIM3_CCMR2_bit.OC2PE=1;
//Разрешаем буферизацию CCR2
TIM3_PSCR=0x00; //Предделитель 16000000 / 1 =
16000000 Hz
TIM3_ARRH=0xFF; //Автозагрузка счетчика старший байт
16000000 / 65535 = 244 Hz
TIM3_ARRL=0xFF; //Автозагрузка счетчика младший байт
TIM3_CCER1_bit.CC2E=1;
//Разрешаем выход CH2
TIM3_CCER1_bit.CC2P=1;
//Включим инверсию CH2 (светодиод горит при CH2=0)
TIM3_CR1_bit.CEN=1; //Разрешаем
счет
Для управления яркостью достаточно
изменять старший 8-битный регистр
сравнения TIM3_CCR2H от 0 до 255, но нужно помнить, что для
изменения значения 16-битного регистра обязательно нужно записать что-либо в регистр
TIM3_CCR2L.
Изменения в
регистре TIM3_CCR2 производятся в основном
цикле программы с использованием временной шкалы на базе TIM4. Отличие от примера из проекта LED-03 заключается в том, что
значение частоты прерываний выбирается кратным 256, поскольку цикл изменения
яркости в одну сторону содержит 256 итераций, а для полного цикла мигания
потребуется 512 итераций, следовательно, чтобы полный цикл продолжался 1
секунду, частота прерываний должна быть равна 512 Гц.
Для этого изменим в инициализации TIM4 значение регистра TIM4_ARR:
TIM4_ARR=244; //Регистр автозагрузки таймера при переполнении 125000 /
244 = 512,2 Hz
Основной цикл программы будет выглядеть следующим образом:
__enable_interrupt(); //Разрешаем прерывания
//Собственно мигание
char i=0;
char flag=0;
Timer1=1; //512 / 1 = 512 Hz
while(1)
{
if(!Timer1) //Прошло 500 мс?
{
Timer1=1; //512 / 1 = 512 Hz
TIM3_CCR2H=i; //Пишем длительность импульса в
регистр сравнения
TIM3_CCR2L=0x00;
if(!flag) //Флаг направления счета
{
if(++i==255) flag=1;
//Увеличение длительности
}
else
{
if(--i==0) flag=0; //Уменьшение
длительности
}
}
}
В переменной i организован
реверсивный 8-битный счетчик, направление счета которого определяется
переменной flag. Значение
счетчика изменяется с частотой 512Гц и записывается в регистр сравнения TIM5, изменяя скважность
импульсов на выходе канала ШИМ, что приводит к изменению яркости свечения
светодиода.
Запишем получившуюся программу (проект LED-05) в МК и увидим
результат – яркость светодиода циклически изменяется от максимальной до
минимальной.
На практике режим ШИМ может быть применен в
светодиодных светильниках, для регулировки яркости светодиодных табло, для
воспроизведения речевых сообщений (в этом случае необходимо повысить несущую
частоту ШИМ за счет уменьшения числа задействованных разрядов счетчика до
8-10).
Еще большие
возможности предоставляет многофункциональный таймер TIM1. Он имеет 4 канала сравнения с
реверсивными счетчиками, каждый канал имеет прямой и инверсный выход с
возможностью программирования задержки deadtime, что дает возможность управлять двухтактными силовыми
ключами, работающими на индуктивную нагрузку. Все возможности TIM1 будут рассмотрены позднее, в рамках
отдельной статьи.
Мы рассмотрели
некоторые возможности МК STM8 S105 на плате Discovery, которые можно было
изучить на практике, не прибегая к помощи паяльника. Еще на данной плате расположена
сенсорная кнопка, состояние которой тоже можно узнать с помощью данного МК. Это
и будет темой следующего урока. Для проведения экспериментов с МК, которые
будут рассматриваться в последующих статьях, Вы можете вооружиться паяльником и
добавить к Discovery недостающие элементы или приобрести плату STM8_QS на нашем сайте. Коды к статье под IAR >> STM8_001_02.zip Коды с использованием заголовков библиотеки от ST (main.c компилируется на Cosmic, Raisonance, IAR). >> STM8_001_STLIB.zip (!) Для Cosmic не забудьте поместить код обработчика прерывания из примера в файл stm8s_it.c |