счет событий в одном направлении TIMER_CntMode_EvtFixedDir счет событий с автоматическим реверсированием TIMER_CntMode_EvtChangeDir */ uint16_t TIMER_CounterDirection; /*!< Определяет направление счета: суммирующий счетчик TIMER_CntDir_Up вычитающий счетчик TIMER_CntDir_Dn */ uint16_t TIMER_EventSource; /*!< Определяет источник событий (тактирования) для таймера. не определен (тактируется от тактовой частоты) TIMER_EvSrc_None Таймер 1 (переключается по обновлению таймера 1) TIMER_EvSrc_TM1 Таймер 2 (переключается по обновлению таймера 2) TIMER_EvSrc_TM2 Таймер 3 (переключается по обновлению таймера 3) TIMER_EvSrc_TM3 Событие в канале 1 TIMER_EvSrc_CH1 Событие в канале 2 TIMER_EvSrc_CH2 Событие в канале 3 TIMER_EvSrc_CH3 Событие в канале 4 TIMER_EvSrc_CH4 Событие на входе ETR TIMER_EvSrc_ETR */ uint16_t TIMER_FilterSampling; /*!< Определяет частоту сэмплирования входных данных(FDTS). Частота сэмплирования равна частоте таймера TIMER_FDTS_TIMER_CLK_div_1 Частота сэмплирования равна частоте таймера/2 TIMER_FDTS_TIMER_CLK_div_2 Частота сэмплирования равна частоте таймера/3 TIMER_FDTS_TIMER_CLK_div_3 Частота сэмплирования равна частоте таймера/4 TIMER_FDTS_TIMER_CLK_div_4 */ uint16_t TIMER_ARR_UpdateMode; /*!< Разрешение мгновенного обновления ARR ARR будет перезаписан в момент записи в ARR TIMER_ARR_Update_Immediately ARR будет перезаписан при завершении счета CNT TIMER_ARR_Update_On_CNT_Overflow */ uint16_t TIMER_ETR_FilterConf;/*!< Определяет конфигурацию фильтра на входе ETR . Более подробно можно посмотреть в файле MDR32F9Qx_timer.h определение @ref IMER_FilterConfiguration */ uint16_t TIMER_ETR_Prescaler; /*!< Определяет предделитель тактовой частоты на входе ETR Более подробно можно посмотреть в файле MDR32F9Qx_timer.h определение @ref TIMER_ETR_Prescaler*/ uint16_t TIMER_ETR_Polarity; /*!< Определяет полярность ETR сигнала. определение в @ref TIMER_ETR_Polarity */ uint16_t TIMER_BRK_Polarity; /*!< Определяет полярность BRK сигнала. определение в @ref TIMER_BRK_Polarity */ } TIMER_CntInitTypeDef; Прерывания или IRQ - это исключения, вызываемые периферийными устройствами или программными запросами. Все прерывания асинхронны по отношению к выполняемым инструкциям, то есть микропроцессор завершает текущую операцию и автоматически вызывает соответствующую процедуру обработки прерывания. Задания: Задание 1 Изменить интервал переключения светодиодов на 500мс. Задание 2 Изменить процедуру обработки прерывания таким образом, чтобы интервалы переключение светодиодов для четных и нечетных светодиодов были различными. 24 Лабораторная работа № 4. Изучение цифро-аналогового преобразователя Цель работы: Изучение основных особенностей работы с цифро-аналоговым преобразователем (ЦАП) при программировании для микроконтроллеров (МК) ARM. Разработка программы для генерации синусоиды. Приборы и материалы: 1. Отладочная плата MDR1986VE91T Rev 4. 2. Программатор J-Link ARM. 3. Блок питания 5В, 1.4А. 4. ПК с установленной средой программирования Keil uVision. 5. Осциллограф. 6. Коаксиальный кабель для соединения двух BNC выходов. Порядок работы: 1. Собрать аппаратную часть по рекомендациям лабораторной работы № 2 2. Открыть проект MDRProject в среде программирования Keil uVision. 3. Подключить в менеджере Manage run-time environment к проекту библиотеки DAC, PORT, RST_CLK , необходимые для работы с ЦАП. 4. Стереть имеющийся в файле main.c исходный код и добавить в начало заголовочные файлы, необходимые в данном проекте: #include #include #include #include 5. Добавить функцию инициализации порта для ЦАП PORT_InitTypeDef PORTEInit; //Объявление структуры void DACPortInit(){ PORT_StructInit(&PORTEInit); // Загрузка умолчаний RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTE, ENABLE); // Тактование /* Настройка DAC 1 (стр.11 спецификации) */ PORTEInit.PORT_Pin = PORT_Pin_9; // Пин 9 PORTEInit.PORT_OE = PORT_OE_OUT; // Порт E PORTEInit.PORT_MODE = PORT_MODE_ANALOG; // Режим Аналоговый PORT_Init(MDR_PORTE, &PORTEInit); // Настройка порта } 6. Добавить функцию инициализации ЦАП void DACInit(){ RST_CLK_PCLKcmd(RST_CLK_PCLK_DAC, ENABLE); //Тактование DAC1_Init(DAC1_AVCC); //Настройка DAC 1 на работу с AVCC DAC1_Cmd(ENABLE); // Активация DAC1 } Первая строчка функции DACInit() подает тактовый сигнал на периферийное устройство ЦАП. Функция DAC1_Init() позволяет настроить источник опорного напряжения для ЦАП1. Последняя строчка разрешает работу ЦАП1. Если предполагается использование внешнего источника, необходимо передать в функцию значение DACn_REF, где . В противном случае, для использования напряжения питания МК в качестве опорного, необходимо передать в функцию значение DACn_AVCC. 7. Добавить в файл исходного кода функцию main(). 25
float a; int main() { RST_CLK_PCLKcmd(RST_CLK_PCLK_RST_CLK, ENABLE); DACPortInit(); DACInit(); while(1){ for (a=0; a<360; a+=5) DAC1_SetData((sinf(a*PI/180)*SCALE + SCALE)/2); } } В приведенном выше фрагменте кода выполняется вызов функции DAC1_SetData() с аргументами, которые вычисляются по функции синуса от равномерно увеличивающегося аргумента. Так как синус находится в пределах от -1 до 1, его необходимо масштабировать в отрезок от 0 до максимального значения. Так как ЦАП 12-битный, максимальное значение равно 0xFFF (12 двоичных единиц). Таким образом, для перевода значений аргумента функции DAC1_SetData() в вольты, необходимо использовать формулу , где Vccявляется напряжением питания МК (3,3В). С учётом сказанного, необходимо добавить определения констант после подключения библиотек. #define PI 3.14159265 #define MAX 2 /*Volt*/ #define SCALE (0x7FF * MAX) / 3.3 8. Скомпилировать исходный код и загрузить его в МК 9. Переключить перемычку DAC_OUT_SEL в положение EXT_CON (рисунок 5.1). Рисунок 4.1 - Перемычка DAC_OUT_SEL 10. Соединить BNC вывод DAC_OUT со входом осциллографа. 11. Настроить вертикальный и горизонтальный масштаб осциллографа до получения синусоид, аналогичных изображенным на рисунке 4.2. 26 Рисунок 4.2 – Осциллограммы работы алгоритма Обратите внимание на ступенчатую структуру синусоиды. Это происходит из-за того, что шаг изменения аргумента синуса 5 градусов, что слишком велико для достижения гладкости синусоиды. Задания: Задание 1 Вывести на осциллограф симметричную пилообразную функцию (рисунок 4.3). Рисунок 4.3 – Симметричная пилообразная функция Задание 2 Вывести на осциллограф параболу в диапазоне . В промежутках между периодичным выводами парабол, поддерживать значение напряжения на уровне 3В. Пример результирующей осциллограммы представлен на рисунке 4.4. Рисунок 4.4 – Парабола 27
Приложение: Полный текст программы #include #include #include #include #define PI 3.14159265 #define MAX 3 /*Volt*/ #define SCALE (0xFFF * MAX) / 3.3 PORT_InitTypeDef PORTEInit; void DACPortInit(){ PORT_StructInit(&PORTEInit); //Load defaults RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTE, ENABLE); PORTEInit.PORT_Pin = PORT_Pin_9; PORTEInit.PORT_OE = PORT_OE_OUT; PORTEInit.PORT_MODE = PORT_MODE_ANALOG; PORT_Init(MDR_PORTE, &PORTEInit); } void DACInit(){ RST_CLK_PCLKcmd(RST_CLK_PCLK_DAC, ENABLE); DAC1_Init(DAC1_AVCC); DAC1_Cmd(ENABLE); } float a; int i; //4 task 2 int main() { RST_CLK_PCLKcmd(RST_CLK_PCLK_RST_CLK, ENABLE); DACPortInit(); DACInit(); while(1){ //Jumper to EXT_CON!!! //* //Base part for (a=0; a<360; a+=5) DAC1_SetData((sinf(a*PI/180)*SCALE + SCALE)/2); //*/ /* //Task 1 for (a=0; a<0xFFF; a+=5) DAC1_SetData(a); for (a=0xFFF-1; a>1; a-=5) DAC1_SetData(a); //*/ /* //Task 2 for (a=-0x7FF; a<0; a+=5) DAC1_SetData(a*a/(0x7FF*0x7FF)*SCALE); for (a=0; a<0x7FF; a+=5) DAC1_SetData(a*a/(0x7FF*0x7FF)*SCALE); DAC1_SetData(SCALE); for (i=0; i<0xFFFF; i++); //*/ } } 28
Лабораторная работа № 5. Изучение аналого-цифрового преобразователя Цель работы: Изучение основных особенностей работы с аналого-цифровым преобразователем (АЦП) и работа со встроенным отладчиком при программировании для микроконтроллеров (МК) ARM. Приборы и материалы: 1. Отладочная плата MDR1986VE91T Rev 4 2. Программатор J-Link ARM 3. Блок питания 5В, 1.4А 4. ПК с установленной средой программирования Keil uVision 5. Цифровой вольтметр Порядок работы: 1. Собрать аппаратную часть по рекомендациям лабораторной работы № 2 2. Открыть проект MDRProject в среде программирования Keil uVision. 3. Подключить в менеджере Manage Run-Time Environment к проекту библиотеки ADC, PORT, RST_CLK необходимые для работы с АЦП. 4. Очистить содержимое файла main.c и добавить ссылки на подключенные библиотеки: #include #include #include #include 5. Добавить в файл исходного кода функцию ADCInit(), в которой произвести инициализацию седьмого канала АЦП1 (на отладочной плате к этому каналу подключено переменное сопротивление). ADC_InitTypeDef ADC; //Общая инициализацинная структура подсистемы АЦП ADCx_InitTypeDef ADC1; //Инициализацинная структура для АЦП1 void ADCInit(){ //Подача тактования на процессор и АЦП RST_CLK_PCLKcmd(RST_CLK_PCLK_RST_CLK | RST_CLK_PCLK_ADC, ENABLE); ADC_StructInit(&ADC); //Заполнение структуры умолч. значениями ADC_Init(&ADC); // Инициализация ADCx_StructInit(&ADC1); ADC1.ADC_ChannelNumber = ADC_CH_ADC7; // Выбор седьмого канала ADC1_Init(&ADC1); //Инициализация прерываний АЦП NVIC_EnableIRQ(ADC_IRQn); NVIC_SetPriority(ADC_IRQn, 0); //Включение прерываний по окончанию преобразования ADC1_ITConfig(ADCx_IT_END_OF_CONVERSION, ENABLE); //Включение АЦП1 ADC1_Cmd(ENABLE); } 6. Добавить в файл исходного кода обработчик прерывания по окончанию аналого-цифрового преобразования. bool conInProgress; //Флаг «в процессе преобразования» unsigned int rawResult; // Необработанный результат unsigned char channel; // Номер канала float result; // Результат в вольтах void ADC_IRQHandler() { // Обработчик прерываний АЦП //Проверка что причина прерывания соответствует концу преобразования if(ADC_GetITStatus(ADC1_IT_END_OF_CONVERSION)){ rawResult = ADC1_GetResult(); //Получение результата channel = (rawResult & 0x1F0000) >> 16; //Сохранение номера канала rawResult &= 0xFFF; //Удаление номера канала из результата 29
//Преобразование результата в вольты result = (float)rawResult / (float)SCALE; conInProgress = false; //Очистка флага «в процессе преобр-я» NVIC_ClearPendingIRQ(ADC_IRQn); //Очистка флага прерывания } } Как видно из кода, результат содержит в себе не только преобразованное значение напряжения на канале, но и номер этого канала, который нужно убрать из результата. На странице 323 спецификации имеется таблица 301 (рисунок 5.1), иллюстрирующая это. Рисунок 5.1 – Структура регистра с результатом преобразования С помощью встроенного в Windows калькулятора в режиме программиста выясняем, что битовая маска, обнуляющая все биты, кроме блока с 20 по 16 включительно, имеет вид, отображенный в шестнадцатеричном формате на рисунке 5.2 (0x1F0000). Рисунок 5.2 – Битовая маска для вычисления номера канала Конъюнктивно применяя данную битовую маску к результату и смещая его на 16 разрядов вправо (для избавления от 16 значимых нулей в конце), выделяем номер канала, который теперь можно сохранить в отдельную переменную. Для выделения результата преобразования применим битовую маску 0xFFF (12 единиц в двоичном представлении). Далее необходимо преобразовать полученный результат из цифрового кода, пропорционального напряжению, в вольты. Для этого результат необходимо поделить на коэффициент SCALE, значение которого предстоит определить экспериментальным путём. 7. Последним штрихом является написание функции main(), с которой начнётся исполнение программы. int i; //Счетчик для задержки цикломint main() { ADCInit(); while(1){ for(i = 0xFFFF; i > 0; i--); //Задержка циклом (плохой вариант) if (!conInProgress){ //Не выполняется ли преобразование? ADC1_Start(); //Начать преобразование! conInProgress = true; //Преобразование выполняется } } } 30 Таким образом, в главном цикле через определенный промежуток времени запускается аналого-цифровое преобразование на седьмом канале первого АЦП. По окончании преобразования, вызывается соответствующий обработчик прерывания, в котором результат преобразования заносится в переменную result. Чтобы данный код заработал, не хватает константы SCALE, и чтобы найти такое ее значение, с помощью которого можно получить результат в вольтах, зададим ее равной единице. #define SCALE 1 8. Подать питание на плату и загрузить программу в микроконтроллер по рекомендациям лабораторной работы №2 9. Поставить перемычку ADC_INP_SEL в положение TRIM для подключения переменного сопротивления в качестве источника сигнала для АЦП (рисунок 5.3). Рисунок 5.3 – Блок аналогового входа 10. Подключить плюсовой вход вольтметра к среднему контакту перемычки (можно замкнуть перемычку в положение TRIM с помощью щупа вольтметра) 11. Подключить минусовой контакт вольтметра к любому общему проводу отладочной платы (удобно использовать любое крепёжное отверстие по краям платы) Рисунок 5.4 – Крепежные отверстия 31 12. Запустить сеанс отладки в программе Keil uVision (Ctrl+F5 или кнопка на панели инструментов) 13. Добавить переменную result в Watch 1 (правый клик по переменной в коде | Add ‘result’ to… | Watch 1 ) и запустить код на исполнение (F5 или кнопка на панели инструментов) 14. Вращать вал переменного сопротивления (TRIM на рисунке 6.4), пока напряжение на АЦП не станет равным 2 вольта. 15. Считать из окна Watch 1 отладчика значение переменной result при известном напряжении на АЦП (рисунок 5.5). 16. Поделить полученное цифровое значение на напряжение, которому оно соответствует. 18. Остановить отладку. 17. Полученный коэффициент вписать в код в качестве константы SCALE. 19. Загрузить код с новым коэффициентом SCALE в МК. 20. Запустить сеанс отладки и убедиться, что переменная result отображает значение, совпадающее с показаниями вольтметра. Рисунок 5.5 – Окно Keil uVision в процессе отладки В окне среды Keil uVision на рисунке 5.5 также открыто окно регистров MDR_ADC. Данное окно можно открыть в меню System Viewer Windows ( ) на панели инструментов. При анализе содержимого регистра ADC1_RESULT, можно подтвердить правильность его описания (рисунок 6.2) и даже понять его структуру при отсутствии описания. Отладка является мощнейшим инструментом разработки и помогает увидеть любые ошибки времени выполнения легко и наглядно. Задание: Используя знания, полученные в лабораторной работе № 5, создать цифровой преобразователь сигнала. При входном уровне сигнала (от переменного сопротивления) от 0 до 1 вольта, он должен выдавать сигнал от 2 до 3 вольт аналогичной формы. Приложение: 32
#include #include #include #include #define delay(T) for(i = T; i > 0; i--) int i; #define SCALE 1252.5 ADC_InitTypeDef ADC; ADCx_InitTypeDef ADC1; void ADCInit(){ RST_CLK_PCLKcmd(RST_CLK_PCLK_RST_CLK | RST_CLK_PCLK_ADC, ENABLE); ADC_StructInit(&ADC); ADC_Init(&ADC); ADCx_StructInit(&ADC1); ADC1.ADC_ChannelNumber = ADC_CH_ADC7; //Switch to TRIM!!! ADC1_Init(&ADC1); //Int NVIC_EnableIRQ(ADC_IRQn); NVIC_SetPriority(ADC_IRQn, 0); ADC1_ITConfig(ADCx_IT_END_OF_CONVERSION, ENABLE); ADC1_Cmd(ENABLE); } bool conInProgress; unsigned int rawResult; unsigned char channel; float result; void ADC_IRQHandler() { if(ADC_GetITStatus(ADC1_IT_END_OF_CONVERSION)){ rawResult = ADC1_GetResult(); channel = (rawResult & 0x1F0000) >> 16; rawResult &= 0x00FFF; result = (float)rawResult / (float)SCALE; conInProgress = false; NVIC_ClearPendingIRQ(ADC_IRQn); } } int main() { ADCInit(); while(1){ delay(0xFFFF); if (!conInProgress){ ADC1_Start(); conInProgress = true; } } } 33
|