1986ВЕ91. Программирование для микроконтроллеров обычно производится на языке Си, однако, при создании микропрограмм для МК архитектуры ARM, повсеместно используются библиотеки, позволяющие отвязать код от конкретного МК и значительно улучшить его переносимость с одного типа микроконтроллера на другой. Библиотеки дают возможность управлять функциями МК с помощью инвариантного программного кода на разных устройствах. Каждая функция МК содержится в своей библиотеке, и они подключаются по мере необходимости. Для управления подключенными к проекту библиотеками необходимо воспользоваться кнопкой «Manage Run-Time Environment» (рисунок 5). Минимальный набор необходимых библиотек состоит из основной системной библиотеки Device / Startup_MDR1986E9x и библиотеки управления тактованием Device / RST_CLK. Однако, используя только функции этих двух библиотек, можно лишь включить МК, а чтобы что-либо сделать с его помощью, понадобится еще хотя бы одна библиотека, для работы со светодиодами нам потребуется библиотека Device / PORT для управления портами ввода/вывода. Таким образом, видеокадр окна с подключенными библиотеками начального проекта MDRProject представлен на рисунке 2.5. 16 Рисунок 2.5 – Менеджер библиотек проекта Рассмотрим программный код, позволяющий мигать светодиодами. #include #include // Подключение заголовочных файлов тех библиотек, // которые непосредственно используются в данном файле исходного кода #define delay(T) for(i = T; i > 0; i--) // Определение функции задержки (см. примечение 1) PORT_InitTypeDef PORTDInit; // Объявление структуры, с помощью которой // будет происходить инициализация порта (см. примечение 2) int i; // Глобальная переменная счетчика, которая используется в функции delay() int main() { // Главная функция, с которой начинается работа программы RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTD, ENABLE); // Включение тактования порта D (устройства) PORTDInit.PORT_Pin = PORT_Pin_10 | // ( см . примечание 2) PORT_Pin_11 | PORT_Pin_12 | PORT_Pin_13 | // Объявляем номера ножек порта , которые PORT_Pin_14; // настраиваются данной структурой 17
PORTDInit.PORT_OE = PORT_OE_OUT; // Конфигурация группы выводов как выход PORTDInit.PORT_FUNC = PORT_FUNC_PORT; // Работа а режиме порта ввода-вывода PORTDInit.PORT_MODE = PORT_MODE_DIGITAL; // Цифровой режим PORTDInit.PORT_SPEED = PORT_SPEED_SLOW; // Низкая частота тактования порта PORT_Init(MDR_PORTD, &PORTDInit); //Инициализация порта Dобъявленной структурой while(1){ //Главный цикл (см. примечение 3) PORT_SetBits(MDR_PORTD, PORT_Pin_10); // Установка единицы на 10 пине в порту Ddelay(0xFFFF); // Задержка (см. примечение 1) PORT_ResetBits(MDR_PORTD, PORT_Pin_10); // Установка нуля на 10 пине в порту DPORT_SetBits(MDR_PORTD, PORT_Pin_11); delay(0xFFFF); PORT_ResetBits(MDR_PORTD, PORT_Pin_11); PORT_SetBits(MDR_PORTD, PORT_Pin_12); delay(0xFFFF); PORT_ResetBits(MDR_PORTD, PORT_Pin_12); PORT_SetBits(MDR_PORTD, PORT_Pin_13); delay(0xFFFF); PORT_ResetBits(MDR_PORTD, PORT_Pin_13); PORT_SetBits(MDR_PORTD, PORT_Pin_14); delay(0xFFFF); PORT_ResetBits(MDR_PORTD, PORT_Pin_14); } } Примечание 1 Поскольку частота работы процессора очень высокая (8 млн. тактов в секунду), глаз не сможет различить мигание светодиода, если написать строчки, включающие и выключающие светодиод подряд. Для формирования задержки в простейшем варианте используют пустой цикл с огромным числом итераций. Недостаток данного способа в том, что пока процессор занят отсчетом итераций, он не может делать что-то еще. Именно такой вид первой программы для МК является классическим. Как и программа, которая выводит на экран надпись “Hello World”, мигание светодиодом с задержкой через цикл не имеет практического применения, но позволяет быстро создать хоть что-то работающее. Примечание 2 При программировании микроконтроллеров ARM с использованием библиотек повсеместно применяется такой элемент языка Си, как структура. Структура похожа на массив, но каждый элемент структуры имеет имя вместо номера, такое имя называют полем, количество полей строго определено. Например, если принять автомобиль как структуру, то его полями могут быть: размер колеса, количество пассажиров, грузоподъемность, мощность двигателя. Синтаксис обращения к элементам структуры: [имя структуры].[имя элемента] В нашем случае структурой является порт микроконтроллера D. Порт микроконтроллера представляет собой набор выводов микроконтроллера (порт отвечает за 16 Выводов), каждый вывод можно настроить на выполнение той или иной функции, более подробно см.[1]. Краткое описание структуры приведено ниже. typedef struct { uint16_t PORT_Pin; /*!< Определяет какие ножки порта будут сконфигурированы например значение поля в двоичном коде 0b0000 0001 0000 0010 конфигурирует 8 и 1 бит. Для задания значений можно воспользоваться стандартными битовыми масками PORT_Pin_x, которые записываются через побитовое ИЛИ: PORT_Pin_1|PORT_Pin_8. */ PORT_OE_TypeDef PORT_OE; /*!< Определяет режим работы порта: ввод или вывод. Может принимать значения PORT_OE_IN - ввод (0) или PORT_OE_OUT - вывод (1)*/ PORT_PULL_UP_TypeDef PORT_PULL_UP; /*!< Определяет включение верхнего подтягивающего резистора PORT_PULL_UP_OFF – выключен (0) или PORT_PULL_UP_ON – включен (1) */ PORT_PULL_DOWN_TypeDef PORT_PULL_DOWN; /*!< Определяет включение нижнего подтягивающего резистора PORT_PULL_DOWN_OFF – выключен(0) или PORT_PULL_DOWN_ON – включен(1) */ PORT_PD_SHM_TypeDef PORT_PD_SHM; /*!< Определяет включение или выключение триггера Шмидта 18 PORT_PD_SHM_OFF – выключен(0) или PORT_PD_SHM_ON – включен(1) при выключенном триггере гистерезис составляет 200мВ, а при включенном 400мВ*/ PORT_PD_TypeDef PORT_PD; /*!< Определяет режим работы ножки. Управляемый драйверPORT_PD_DRIVER (0) или PORT_PD_OPEN (1) илиоткрытыйсток*/ PORT_GFEN_TypeDef PORT_GFEN; /*!< Определяет режим работы входного фильтра ножки. Выключен PORT_GFEN_OFF (0) иливключен PORT_GFEN_ON (1)*/ PORT_FUNC_TypeDef PORT_FUNC; /*!< Определяет режим работы вывода порта:- Порт PORT_FUNC_PORT (0); - Основная функция PORT_FUNC_MAIN (1); - Альтернативная функция PORT_FUNC_ALTER (2); - Переопределенная функция PORT_FUNC_OVERRID (3);*/ PORT_SPEED_TypeDef PORT_SPEED; /*!< Определяет скорость работы порта:зарезервировано (передатчик отключен) PORT_OUTPUT_OFF (0); медленный фронт (порядка 100 нс) PORT_SPEED_SLOW (1); быстрый фронт (порядка 20 нс) PORT_SPEED_FAST (2); максимально быстрый фронт (порядка 10 нс) PORT_SPEED_MAXFAST (3);*/ PORT_MODE_TypeDef PORT_MODE; /*!< Определяет режим работы контроллера:0 – аналоговый PORT_MODE_ANALOG = 0x0, 1 – цифровой PORT_MODE_DIGITAL = 0x1*/ }PORT_InitTypeDef; Примечание 3 Порядок инициализации устройств МК следующий: 1. Определение структуры соответствующего типа 2. Включение тактирования устройства 3. Заполнение элементов структуры требуемыми значениями 4. Инициализация устройства с параметрами определенными в структуре Примечание 4 Очередная особенность программирования для МК заключается в том, что программа никогда не заканчивается. Представьте, что вы сделали фонарик с переключением мощности по кнопке и он перестает работать после того, как вы один раз переключили все режимы. Правильный вариант состоит в том, чтобы после последнего режима переходить обратно на первый. Таким образом, необходимо делать программу, в которой при любых внешних воздействиях будет выполняться некоторый код. Из этих соображений, в программе для МК существует такое понятие, как главный цикл. Это бесконечный цикл внутри функции main(). Все инструкции внутри главного цикла бесконечно повторяются, пока подается питание на МК. Все, что до главного цикла, выполняется лишь однажды, при включении. Главный цикл while(1). Задания: Задание 1 (по вариантам) 1. Изменить программу таким образом, чтобы светодиоды «бегали» в обоих направлениях. 2. Изменить программу таким образом, чтобы светодиоды «бежали» из центра в края. 3. Изменить программу таким образом, чтобы светодиоды «бежали» из краев в центр. 4. Изменить программу таким образом, чтобы светодиоды «бегали» с задержкой в один светодиод (в каждый момент времени должны гореть два светодиода). 5. Изменить программу таким образом, чтобы чётные светодиоды переключались в 2 раза быстрее нечётных. Задание 2 (общее) Организовать считывание кнопок джойстика на отладочной плате и реагировать на их состояние соответствующими светодиодами, пользуясь следующей информацией: 19 • Кнопки джойстика располагаются в порту C, номера пинов с 10 по 14. • Константа поля PORT_OE структуры PORT_InitTypeDef для конфигурации группы портов как вход имеет вид PORT_OE_IN • Кнопки на плате при нажатии соединяются с цепью GND (землей). • Сигнатура функции для чтения состояния пина имеет вид: uint8_t PORT_ReadInputDataBit( MDR_PORT_TypeDef* PORTx, uint32_t PORT_Pin ) 20
Лабораторная работа № 3. Изучение работы таймера Цель работы: Изучение основных особенностей работы с таймерами с использованием прерывания при программировании для микроконтроллеров (МК) ARM. Приборы и материалы: 1. Отладочная плата MDR1986VE91T Rev 4 2. Программатор J-Link ARM 3. Блок питания 5В, 1.4А 4. ПК с установленной средой программирования Keil uVision Порядок работы: 1. Собрать аппаратную часть по рекомендациям лабораторной работы № 1 2. Открыть проект MDRProject в среде программирования Keil uVision. 3. Подключить к проекту библиотеку TIMER, необходимую для работы с таймерами. Рисунок 3.1 – Библиотеки проекта для работы с таймерами 4. Добавить в заголовок основного файла исходного кода main.c ссылку на заголовочный файл библиотеки TIMER: #include 5. Вынести содержимое главного цикла в отдельную функцию, которая при каждом вызове переключает светодиод на следующий: uint8_t cur_i; void NextLED(){ switch(cur_i++ % 5) { case 0: PORT_ResetBits(MDR_PORTD, PORT_Pin_14); PORT_SetBits(MDR_PORTD, PORT_Pin_10); break; case 1: PORT_ResetBits(MDR_PORTD, PORT_Pin_10); PORT_SetBits(MDR_PORTD, PORT_Pin_11); break; case 2: PORT_ResetBits(MDR_PORTD, PORT_Pin_11); 21
PORT_SetBits(MDR_PORTD, PORT_Pin_12); break; case 3: PORT_ResetBits(MDR_PORTD, PORT_Pin_12); PORT_SetBits(MDR_PORTD, PORT_Pin_13); break; case 4: PORT_ResetBits(MDR_PORTD, PORT_Pin_13); PORT_SetBits(MDR_PORTD, PORT_Pin_14); break; } } 6. Инициализировать переменную cur_i нулём в функции main() до главного цикла, чтобы при первом вызове директиве cur_i++ , было к чему прибавлять единицу. 7. Создать функцию TimerInit() , в которой производится инициализация аппаратного таймера TIMER1 и соответствующего прерывания по переполнению, и вызвать ее в функции main() до главного цикла while(1) : //Объявление инициализационной структуры TIMER_CntInitTypeDef TIM1Init; void TimerInit(){ // Включение тактирования таймера RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER1, ENABLE); //Установка первого делителя тактовой частоты таймера TIMER_BRGInit(MDR_TIMER1, TIMER_HCLKdiv1); //Загрузка значений по - умолчанию в структуру TIM1Init TIMER_CntStructInit(&TIM1Init); TIM1Init.TIMER_Prescaler = 8000; // Второй делитель частоты TIM1Init.TIMER_Period = 1000; // Период до обновлния или основание счета TIMER_CntInit(MDR_TIMER1, &TIM1Init); // Настройка прерывания NVIC_EnableIRQ(Timer1_IRQn); // Включение прерываний от TIMER1 NVIC_SetPriority(Timer1_IRQn, 0); //Установка приоритета прерываний 0 -15 //Включение прерывания при равенстве нулю значения TIMER1 TIMER_ITConfig(MDR_TIMER1, TIMER_STATUS_CNT_ZERO, ENABLE); // Запуск таймера TIMER_Cmd(MDR_TIMER1, ENABLE); } 8. Написать обработчик прерывания TIMER1, в котором вызвать функцию NextLED() void Timer1_IRQHandler() { //Проверка что причина прерывания – обновление таймера if(TIMER_GetITStatus(MDR_TIMER1, TIMER_STATUS_CNT_ZERO)){ NextLED(); //Очистка флага прерывания (это необходимо делать в конце) TIMER_ClearITPendingBit(MDR_TIMER1, TIMER_STATUS_CNT_ZERO); } } 9. Загрузить и отладить программу по рекомендациям в лабораторной работе № 2. Полный текст программы можно найти в файле Timer_Int.c 10. Выполнить индивидуальное задание. Сведения для выполнения Рисунок 2 иллюстрирует работу таймера в микроконтроллерах. После запуска, таймер начинает прибавлять к начальному значению счетчика фиксированное число через фиксированный промежуток времени, который устанавливается двумя делителями тактовой частоты МК. При достижении таймером значения периода, таймер «Обновляется» и сбрасывается в 0. При этом срабатывает соответствующее прерывание (если оно настроено). Можно настроить прерывание, возникающее при данном событии. Таким образом, в коде выше, общий делитель частоты равен 8000, что при делении на тактовую частоту МК 8 МГц, дает тактовую частоту таймера 1000 раз в секунду. Период установлен в 1000, что позволяет 22
генерировать прерывания каждую секунду. Рисунок 3.2 – Иллюстрация работы таймера Преимущество такого способа отсчета временных интервалов в том, что главный цикл при этом пуст и МК в промежутке между прерываниями может выполнять любой другой код. Обычно программа для МК организована таким образом, чтобы прерывания меняли переменную состояния, а главный цикл ее проверял и выполнял различные действия в зависимости от текущего состояния. Таймеры МК 1986ВЕ9Т выполнены на основе 16-битного перезагружаемого счетчика, который синхронизируется с выхода 16-битного предделителя. Перезагружаемое значение хранится в отдельном регистре. Счет может быть прямой, обратный или двунаправленный (сначала прямой до определенного значения, а затем обратный). Каждый из трех таймеров микроконтроллера содержит 16-битный счетчик, 16-битный предделитель частоты и 4-канальный блок захвата/сравнения. Их можно синхронизировать системной синхронизацией, внешними сигналами или другими таймерами. Помимо составляющего основу таймера счетчика, в каждый блок таймера также входит четырехканальный блок захвата/сравнения. Данный блок выполняет как стандартные функции захвата и сравнения, так и ряд специальных функций. Таймеры с 4 каналами схем захвата и ШИМ с функциями формирования «мертвой зоны» и аппаратной блокировки. Каждый из таймеров может генерировать прерывания и запросы прямого доступа к памяти. Инициализация таймера и системы прерываний состоит из нескольких этапов: 1. Включение тактирования таймера. 2. Установка первого делителя тактовой частоты таймера 3. Заполнение элементов структуры требуемыми значениями 4. Инициализация устройства с параметрами определенными в структуре 5. Настройка и включение прерываний 6. Разрешение работы таймера Рассмотрим структуру для инициализации таймера подробнее: typedef struct { uint16_t TIMER_IniCounter; /*!< Определяет начальное значение счетчика, может быть задан в диапазоне 0x0000 and 0xFFFF. */uint16_t TIMER_Prescaler; /*!< определяет второй делитель тактовой частоты таймера. Может быть задан в диапазоне 0x0000 and 0xFFFF. Расчет тактовой частоты таймера можно вести по формулеCLK = TIMER_CLK/(TIMER_Prescaler + 1) */ uint16_t TIMER_Period; /*!< Определяет основание счета счетчика которое зашлется в регистр Auto-Reload Register (ARR) при следующем обновлении. 23 Может быть задан в диапазоне 0x0000 and 0xFFFF.*/ uint16_t TIMER_CounterMode; /*!< Определяет режим работы таймера:счет тактовой частоты в одном направленииTIMER_CntMode_ClkFixedDir счет тактовой частоты с автоматическим реверсированием TIMER_CntMode_ClkChangeDir перейти в каталог файлов |