А.П.Евдокимов, Л.Л.Владимиров Программирование микроконтроллера К1986ВЕ92QI компании Миландр (1186478), страница 5
Текст из файла (страница 5)
Приэтом, понятно, средний за период уровень сигнала будет также переменным. Данный режим широко используется, когда необходимо регулировать яркость свечения лампы накаливания, частоту вращениядвигателя постоянного тока и т.п.О режиме «ШИМ» поговорим в дальнейшем, а пока займемсянастройкой таймера в качестве счетчика.Прежде всего, таймер должен быть подключен к тактовому генератору, подобно тому, как мы это делали при настройке порта:RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER1, ENABLE);37Для дальнейшей настройки таймера потребуется несколькоструктур.
Чтобы оценить возможности настройки таймера, приведемих в полном объеме.Начнем со структуры TIMER_CntInitTypeDef. Структура имеетследующие поля: TIMER_Prescaler – значение величины предделителя; TIMER_Period – период таймера; TIMER_CounterMode – режим счета; TIMER_CounterDirection – направление счета; TIMER_EventSource – источник событий для таймера; TIMER_FilterSampling – фильтр событий; TIMER_ARR_UpdateMode – режим сброса счетчика; TIMER_ETR_FilterConf – параметры выхода ETR; TIMER_ETR_Prescaler – параметры предделителя фильтра выхода; ETR TIMER_ETR_Polarity – полярность выхода; ETR TIMER_BRK_Polarity – полярность выхода BRK.Структура TIMER_ChnInitTypeDef имеет следующие поля: TIMER_CH_Mode – задает режим работы таймера; TIMER_CH_REF_Format – формат выработки сигнала REF в режиме ШИМ; TIMER_CH_Number – номер канала таймера.И, наконец, поля структуры TIMER_ChnOutInitTypeDef: TIMER_CH_DirOut_Polarity – полярность выхода CHx; TIMER_CH_DirOut_Source – сигнал на выходе CHx; TIMER_CH_DirOut_Mode – сигнал на выходе CHx; TIMER_CH_NegOut_Polarity – полярность инверсного выходаCHx; TIMER_CH_NegOut_Source – сигнал на инверсном выходе CHx; TIMER_CH_NegOut_Mode – сигнал на инверсном выходе CHx; TIMER_CH_Number – номер канала.Строго говоря, все эти поля надо прописать, однако некоторыеиз них являются типичными и встречаются очень часто, поэтому могут быть установлены «по умолчанию».
Для этого в программе необходимо записать следующую строку:TIMER_CntStructInit(&TIM1Init);38При этом произойдет заполнение полей с помощью структуры, описанной в библиотеке в файле MDR32F9Qx_timer.c:void TIMER_CntStructInit(TIMER_CntInitTypeDef*TIMER_CntInitStruct){TIMER_CntInitStruct->TIMER_IniCounterTIMER_CntInitStruct->TIMER_PrescalerTIMER_CntInitStruct->TIMER_PeriodTIMER_CntInitStruct->TIMER_CounterModeTIMER_CntMode_ClkFixedDir;TIMER_CntInitStruct->TIMER_CounterDirectionTIMER_CntInitStruct->TIMER_EventSourceTIMER_EvSrc_None;TIMER_CntInitStruct->TIMER_FilterSamplingTIMER_FDTS_TIMER_CLK_div_1;TIMER_CntInitStruct->TIMER_ARR_UpdateModeTIMER_ARR_Update_Immediately;TIMER_CntInitStruct->TIMER_ETR_FilterConfTIMER_Filter_1FF_at_TIMER_CLK;TIMER_CntInitStruct->TIMER_ETR_PrescalerTIMER_ETR_Prescaler_None;TIMER_CntInitStruct->TIMER_ETR_PolarityTIMER_ETRPolarity_NonInverted;TIMER_CntInitStruct->TIMER_BRK_PolarityTIMER_BRKPolarity_NonInverted;}= 0;= 0;= 0;== TIMER_CntDir_Up;=======После настройки «по умолчанию» отдельные позиции можноскорректировать.
Что же остается настроить? Совсем немного.Настройка делителя тактовой частоты осуществляется с помощью параметра TIMER_HCLK, который участвует в выражении:TIMER_BRGInit(MDR_TIMER1, TIMER_HCLKdiv1);Данный параметр может принимать следующие значения: TIMER_HCLKdiv1: нет деления входной частоты; TIMER_HCLKdiv2: делитель входной частоты на 2 (т.е. замедлитпереключение таймера в 2 раза); TIMER_HCLKdiv4: делитель входной частоты на 4; TIMER_HCLKdiv8: делитель входной частоты на 8; TIMER_HCLKdiv16: делитель входной частоты на 16; TIMER_HCLKdiv32: делитель входной частоты на 32; TIMER_HCLKdiv64: делитель входной частоты на 64;39 TIMER_HCLKdiv128: делитель входной частоты на 128.Зададим коэффициент деления предделителя тактовой частоты:TIM1Init.TIMER_Prescaler = 8000;Это еще один способ изменить частоту переключения таймера: чембольше число – тем медленнее работает таймер.
Значение 16разрядного предделителя могут быть от 1 до 65535 (т.е. 216 – 1).Например, тактовая частота составляет 8 МГц, делитель тактовой частоты не применялся, коэффициент деления предделителя 8000. Тогдатаймер будет считать с частотой 8·106 Гц / 8·103 = 103 Гц, или периодом 1 мс.Зададим период срабатывания таймера:TIM1Init.TIMER_Period = 500;В этом случае таймер досчитает до 500, выдаст сигнал и обнулится, а затем снова будет считать до 500.
Значение периода можнозадавать числами от 1 до 65535. Таким образом, в рассматриваемомпримере период срабатывания таймера составляет 1 мс ∙ 500 = 500 мс,то есть таймер будет обнуляться 2 раза в секунду (частота – 2 Гц).Завершается настройка указанием названия таймера, к которомуприменена настройка, аналогично тому, как это делалось для порта:TIMER_CntInit(MDR_TIMER1, &TIM1Init);Теперь поговорим о сигнале, который выдает таймер. Его можно использовать для прерывания основной программы и выполнениякаких-либо действий всякий раз, когда таймер завершает свой счет –например, включение и выключение светодиода.
Работа по отдельнойпрограмме после возникновения события прерывания, как вы помните, называется обработкой прерывания. Преимущество такого подходав сравнении с формированием временных интервалов с помощьюциклического программного счета заключается в том, что формирование интервалов времени происходит аппаратно, а микроконтроллерпри этом может выполнять основную программу.Для разрешения прерываний от таймера 1 необходимо записать:NVIC_EnableIRQ(TIMER1_IRQn);Здесь дается лишь общее разрешение на прерывание от таймераTIMER1, но пока не ясно, при каком состоянии таймера оно произойдет.
Установим возникновение прерывания при равенстве нулю значения TIMER1:TIMER_ITConfig(MDR_TIMER1, TIMER_STATUS_CNT_ZERO, ENABLE);40Прерывание в микроконтроллере может быть вызвано различными источниками, поэтому необходимо задать их приоритет, т.е.указать микроконтроллеру, какое прерывание необходимо обработатьв первую очередь, во вторую и т.д. Чем меньше число, обозначающееприоритет, тем выше уровень приоритета, тем раньше будет обработано прерывание. Вот так устанавливается приоритет, равный нулю:NVIC_SetPriority(TIMER1_IRQn, 0);Теперь можно запустить таймер:TIMER_Cmd(MDR_TIMER1, ENABLE);Итак, таймер начал считать с нужной нам частотой до заданногонами значения, и вот он обнулился и должен приступить к обработкепрерывания .
Делается это с помощью процедуры:void Timer1_IRQHandler()Допустим, если состояние таймера стало равным нулю, то следует выполнить процедуру LED(). Опишем данную ситуацию так:if (TIMER_GetITStatus(MDR_TIMER1, TIMER_STATUS_CNT_ZERO))LED();Прерывание всегда сопровождается появлением флага, или логической единицы, в определенном регистре. Флаг не только сигнализирует о том, что прерывание возникло, но и не позволяет до завершения обработки прерывания приступать к обработке следующего прерывания.
Флаг должен быть сброшен программно:TIMER_ClearITPendingBit(MDR_TIMER1, TIMER_STATUS_CNT_ZERO);Рассмотрим программу, которая заставляет поочередно зажигать светодиоды VD3 и VD4, расположенные на отладочной плате:// Подключение заголовочных файлов тех библиотек,// которые непосредственно используются в данной программе#include <MDR32F9Qx_port.h>#include <MDR32F9Qx_rst_clk.h>#include <MDR32F9Qx_timer.h>// Процедура инициализация портаvoid PortsInit(){// Указание типа структуры и имени структуры41PORT_InitTypeDef Nastroyka;// Включение тактирования порта CRST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE);// Заполнение структуры значениями по умолчаниюPORT_StructInit(&Nastroyka);// Объявление номера линии порта,// которая настраивается данной структуройNastroyka.PORT_Pin = PORT_Pin_0;// Конфигурация группы линий как выходNastroyka.PORT_OE = PORT_OE_OUT;// Работа в режиме порта ввода-выводаNastroyka.PORT_FUNC = PORT_FUNC_PORT;// Цифровой режимNastroyka.PORT_MODE = PORT_MODE_DIGITAL;// Низкая скорость переключения (пологий фронт)Nastroyka.PORT_SPEED = PORT_SPEED_SLOW;// Инициализация порта C объявленной структуройPORT_Init(MDR_PORTC, &Nastroyka);}// Процедура включения/выключения светодиодаvoid LED(){// Объявление переменной istatic uint8_t i = 0;// Инкрементируя i, находить остаток от деления i на 2switch (i++ % 2){// В случае если остаток равен нулю, сбросить бит по линии 0case 0:PORT_ResetBits(MDR_PORTC, PORT_Pin_0);// В противном случае перейти к следующей командеbreak;// В случае если остаток равен единице, установить бит по линии 0case 1:PORT_SetBits(MDR_PORTC, PORT_Pin_0);// В противном случае перейти к следующей командеbreak;}}// Процедура инициализации таймераvoid TimerInit(){// Указание типа структуры и имени структурыTIMER_CntInitTypeDef TIM1Init;42// Включение тактированияRST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER1, ENABLE);// Заполнение структуры значениями по умолчаниюTIMER_CntStructInit(&TIM1Init);// Настройка делителя тактовой частотыTIMER_BRGInit (MDR_TIMER1, TIMER_HCLKdiv1);// Задание предделителя тактовой частотыTIM1Init.TIMER_Prescaler = 8000;// Задание периода срабатывания таймераTIM1Init.TIMER_Period = 500;// Инициализация порта таймера объявленной структуройTIMER_CntInit (MDR_TIMER1, &TIM1Init);// Включение прерыванийNVIC_EnableIRQ (TIMER1_IRQn);// Установка приоритета прерыванийNVIC_SetPriority (TIMER1_IRQn, 0);// Включение прерывания при равенстве нулю значения TIMER1TIMER_ITConfig(MDR_TIMER1, TIMER_STATUS_CNT_ZERO, ENABLE);// Запуск таймераTIMER_Cmd(MDR_TIMER1, ENABLE);}// Процедура обработки прерывания, вызванного таймеромvoid Timer1_IRQHandler(){// Если таймер сброшен в ноль, вызвать процедуру LED()// и сбросить флаг прерыванияif (TIMER_GetITStatus(MDR_TIMER1, TIMER_STATUS_CNT_ZERO)){LED();TIMER_ClearITPendingBit(MDR_TIMER1, TIMER_STATUS_CNT_ZERO);}}// Объявление главной функцииint main(){// Вызов процедуры инициализации портаPortsInit();// Вызов процедуры инициализации таймераTimerInit();// Пустой циклwhile (1){}}43Если с настройкой портов и таймеров всё более или менее ясно,то о программировании на языке Си разговор только начинается.
Влабораторном практикуме мы не можем позволить себе систематическое изложение основ программирования, поэтому пойдем от частного к общему.Итак, что означает фрагментwhile (1){}в конце программы?Полностью конструкция, содержащая оператор while, выглядит так:while (условие){Блок операций}То есть пока выполняется условие, выполняется блок операций.А вот если вместо условия записать константу, как в нашем случае, то блок операций будет выполнятся всегда, ведь константа не изменится. Но у нас и операций-то нет – значит, мы создали «пустой»цикл.