Автор работы: Пользователь скрыл имя, 09 Сентября 2014 в 09:42, лекция
В микроконтроллерах семейства STM32L1xx можно организовать отсчет времени по тактам системных часов. По умолчанию в момент запуска системные часы ядра CORTEX – 3M устанавливаются на тактовую частоту f=2000000 гц, обеспечивая длительность одного такта τ=1/f=1/2000000 с =0.0000005 с =0.5 млс. Измерители времени или таймеры способны автономно подсчитывать системные такты до заданной величины.
#define TIM11_DisInterrupt TIM11->DIER &= ~TIM_IT_Update
// разрешить использование этого прерывания другими источниками
#define TIM11_EnPending TIM11->SR |= TIM_IT_Update
// запретить использование этого прерывания другими источниками
#define TIM11_DisPending TIM11->SR &= ~TIM_IT_Update
//----------------------------
// включить канал и его
void Timer_NVIC( uint8_t channel,
uint8_t preemptionPriority,
uint8_t subPriority )
{
uint8_t tmp = 0x00, tmppre = 0x00, tmpsub = 0x0F;
assert_param(IS_NVIC_
assert_param(IS_NVIC_SUB_
tmp = ( 0x700 - ( (SCB->AIRCR) & (uint32_t)0x700 ) ) >> 0x08;
tmppre = ( 0x4 - tmp );
tmpsub = tmpsub >> tmp;
tmp = (uint32_t)preemptionPriority << tmppre;
tmp |= (uint8_t)( subPriority & tmpsub );
tmp = tmp << 0x04;
NVIC->IP[channel] = tmp;
NVIC->ISER[ channel >> 0x05 ] =
(uint32_t)0x01 << (channel & (uint8_t)0x1F);
}
//----------------------------
void Timer_DIS( uint8_t channel ) // выключить IRQ канал
{
NVIC->ICER[channel >> 0x05] =
(uint32_t)0x01 << (channel & (uint8_t)0x1F);
}
//----------------------------
#define TIM2_RCC RCC_APB1ENR_TIM2EN
#define TIM3_RCC RCC_APB1ENR_TIM3EN
#define TIM4_RCC RCC_APB1ENR_TIM4EN
#define TIM6_RCC RCC_APB1ENR_TIM6EN
#define TIM7_RCC RCC_APB1ENR_TIM7EN
//----------------------------
// T = 0.5 sec.; f = 1 / T = 2 hz
// fs = 2000000 hz
// N = fs * T = 1000000
// N = u * v = 1000000
// reload = u, prescaler = v
//----------------------------
void Init_TIMx1( TIM_TypeDef* TIMx, uint32_t TIMx_RCC,
uint16_t reload, uint16_t prescaler,
uint8_t TIMx_IRQn,
uint8_t preemptionPriority,
uint8_t subPriority )
{
RCC->APB1ENR |= TIMx_RCC; // включить часы TIMx
TIMx->ARR = reload; // число серий на прерывание
TIMx->PSC = prescaler; // число тактов в серии
TIMx->DIER |= TIM_DIER_UIE;// разрешить прерывания от таймера
// установить приоритеты канала прерываний
Timer_NVIC( TIMx_IRQn, preemptionPriority, subPriority );
}
//----------------------------
#define TIM9_RCC RCC_APB2ENR_TIM9EN
#define TIM10_RCC RCC_APB2ENR_TIM10EN
#define TIM11_RCC RCC_APB2ENR_TIM11EN
//----------------------------
void Init_TIMx2( TIM_TypeDef* TIMx, uint32_t TIMx_RCC,
uint16_t reload, uint16_t prescaler,
uint8_t TIMx_IRQn,
uint8_t preemptionPriority,
uint8_t subPriority )
{
RCC->APB2ENR |= TIMx_RCC; // включить часы TIMx
TIMx->ARR = reload; // число серий на прерывание
TIMx->PSC = prescaler; // число тактов в серии
TIMx->DIER |= TIM_DIER_UIE;// разрешить прерывания от таймера
// установить приоритеты канала прерываний
Timer_NVIC( TIMx_IRQn, preemptionPriority, subPriority );
}
//----------------------------
void Timer_OnePulseMode( TIM_TypeDef* TIMx, uint16_t mode)
{
assert_param( IS_TIM_ALL_PERIPH( TIMx ) ); // номер таймера
assert_param( IS_TIM_OPM_MODE( mode ) ); // тип режима
TIMx->CR1 &= (uint16_t)~( (uint16_t)TIM_CR1_OPM );
TIMx->CR1 |= mode;
}
Функция Init_TIMx2() предназначена для инициализации таймеров TIM9, TIM10, NIM11, которые работают по второй системной периферийной шине APB2. В остальном тексты функций Init_TIMx2() и Init_TIMx1() полностью совпадают.
2.3. Мигающий свет в проекте FT12 с файлом Timer_Init.h.
Проект FT12 в точности повторяет действия предыдущего проекта FT11. Отличие в том, что все вспомогательные функции и макросы находятся в соответствующих подключаемых файлах.
// Project FT12
// Мигание зеленого света с частотой 1 hz по таймеру 2
// Файл Timer_Init.h для всех таймеров
//
#include "stm32l1xx.h"
#include "stm32l_discovery_lcd.h"
#include "Port_Init.h"
#include "Light_Lib.h"
#include "Display_Init.h"
#include "Display_Lib.h"
#include "Timer_Init.h"
//----------------------------
uint16_t DisplayArray[6]; // строка символов дисплея
uint32_t n = 0;
//----------------------------
int main(void)
{
Init_PortABC(); // инициализация портов A, B, C
Init_BlueGreenLight();// инициализация синего, зеленого света
Init_Display_PortABC(); // альтернативные функции дисплея
Init_LCD();
DisplayUint32Convertion( DisplayArray, n ); // строка для n
LCD_GLASS_DisplayStrDeci( DisplayArray ); // показать число
LIGHT_ON( LIGHT_PORT, BLUE_LIGHT ); // включить синий свет
// T = 1 sec.; f = 1 / T = 1 hz
// N = fs * T / 2 = 1000000
Init_TIMx1( TIM2, TIM2_RCC, 1000, 1000, TIM2_IRQn, 0, 0 );
TIM2_Start;
while(1); // цикл продолжения в реальном времени
}
//----------------------------
void TIM2_IRQHandler(void)
{
// запретить использование этого прерывания другими источниками
TIM2_DisPending; // запретить прерывание этой функции
LIGHT_PORT->ODR ^= GREEN_LIGHT; // переключить зеленый свет
}
2.4. Кнопка включения отсчетов мигания света в проекте FT13.
В проекте FT13 таймер TIM10 обеспечивает частоту 2 гц для мигания зеленого светодиода с частотой 1 гц. Номер вспышки выводится на дисплей. После включения устройства запуск таймера TIM10 производится по нажатию кнопки. Включение и выключение света, отсчет вспышек и вывод номера вспышки на дисплей синхронизированы в функции обслуживания прерывания TIM10_IRQHandler(). Имя этой функции необходимо внести в вектор прерывания в файле startup_stml1xx_md.s проекта FT13 по аналогии с предыдущей программой FT11 (п. 2.1.6).
// Project FT13
// Кнопка старта миганий света 1 hz и учета отсчетов на дисплее
//
#include "stm32l1xx.h"
#include "stm32l_discovery_lcd.h"
#include "Port_Init.h"
#include "Light_Lib.h"
#include "Button_Lib.h"
#include "Display_Init.h"
#include "Display_Lib.h"
#include "Timer_Init.h"
//----------------------------
uint16_t DisplayArray[6]; // строка символов дисплея
uint32_t n = 0;
bool ButFlag = true; // ожидание нажатия кнопки PA0
//----------------------------
int main(void)
{
Init_PortABC(); // инициализация портов A, B, C
Init_BlueGreenLight(); // инициализация синего, зеленого света
Init_Display_PortABC(); // альтернативные функции дисплея
Init_LCD();
// T = 1 sec.; f = 1 / T = 1 hz
// N = fs * T / 2 = 1000000
Init_TIMx2( TIM10, TIM10_RCC, 1000, 1000, TIM10_IRQn, 0, 0);
DisplayUint32Convertion( DisplayArray, n ); // строка для n
LCD_GLASS_DisplayStrDeci( DisplayArray ); // показать число
LIGHT_ON( LIGHT_PORT, BLUE_LIGHT ); // включить синий свет
while( ButFlag ) // ждать нажатия кнопки
if( CHECK_BUTTON(BUT_PORTA, BUT_PIN0) != 0 ) // нажатие
ButFlag = false; // снять ожидание
TIM10_Start;
while(1); // цикл продолжения в реальном времени
}
//----------------------------
void TIM10_IRQHandler(void)
{
// запретить использование этого прерывания другими источниками
TIM10_DisPending; // запретить прерывание этой функции
LIGHT_PORT->ODR ^= GREEN_LIGHT; // переключить зеленый свет
if( LIGHT_PORT->ODR != 0 ) // свет горит
{
n++;
DisplayUint32Convertion( DisplayArray, n ); // строка для n
LCD_GLASS_DisplayStrDeci( DisplayArray ); // показать число
}
}
Подключаемые файлы #include "Display_Init.h" и #include "Display_Lib.h" обеспечивают взаимодействие с дисплеем (п. 1.13, 1.15). Подключаемый файл #include "Light_Lib.h" (п. 1.8) содержит макросы светодиодов. В подключаемом файле #include "Button_Lib.h" (п. 1.9) находятся макросы кнопки, реализованной с помощью иголки PA0 порта A. Функции таймера размещены в подключаемом файле #include "Timer_Init.h" (п. 2.2).
Старт отсчетов реализован в главной функции main() с помощью блокирующего цикла:
while( ButFlag ) // ждать нажатия кнопки
if( CHECK_BUTTON(BUT_PORTA, BUT_PIN0) != 0 ) // нажатие
ButFlag = false; // снять ожидание
После нажатия кнопки цикл открывается и выполняется следующая инструкция TIM10_Start. После этого, прерывания таймера TIM10 будут обращаться к функции обслуживания прерывания TIM10_IRQHandle(), которая выводит количества миганий на дисплей синхронно с миганием света.
2.5. Время выполнения программных элементов в проекте FT21.
Задачей проекта является оценка быстродействия или количества тактов, затрачиваемых на выполнение инструкций в кодировке языка Си. Для этого используется счетчик таймера TIM10, который запускается перед интересующим фрагментом программы. Далее счетчик работает автономно, подсчитывая такты на шине. Одновременно процессор ядра выполняет фрагмент программы. Когда фрагмент выполнен, счетчик останавливается. Для этих действий можно обойтись без прерываний, которые неизбежно затратят несколько тактов процессора на переключения. Чтобы счетчик подсчитывал именно такты, следует сбросить или установить в единицу количество тактов внутри серии. Тогда каждый такт на шине будет увеличивать на единицу счетчик серий таймера. Один такт на шине – один такт процессора при выполнении программного фрагмента. Умножив количество тактов на временной интервал такта, получим время выполнения фрагмента. В проекте FT21 время не вычисляется, но количество тактов выводится на дисплей.
2.5.1. Главная функция main.
Прежде всего, необходимо убедиться, сколько тактов зафиксирует счетчик, если программный фрагмент пуст, т.е. в нем нет инструкций на языке Си для выполнения. Следовательно, между стартом и остановкой счетчика не должно быть ни одной инструкции. Надеемся, что на дисплее появится 0. Однако на дисплее находится значение 9 тактов. Эти такты можно отнести на внутреннюю организацию программы (п.2.5.3). Ниже представлен главный файл main.c проекта FT21.
// Project FT21
// Количество тактов на старт и остановку счетчика
//
#include "stm32l1xx.h"
#include "stm32l_discovery_lcd.h"
#include "stm32l1xx_tim.h"
#include "Port_Init.h"
#include "Light_Lib.h"
#include "Display_Init.h"
#include "Display_Lib.h"
#include "Timer_Init.h"
//----------------------------
uint16_t
k = 0;
uint32_t
n = 0;
uint16_t DisplayArray[6]; // строка символов дисплея
//----------------------------
int main(void)
{
Init_PortABC(); // инициализация портов A, B, C
Init_BlueGreenLight();// инициализация синего и зеленого света
Init_Display_PortABC(); // альтернативные функции дисплея
Init_LCD();
Init_TIMx2_CNT( TIM10, TIM10_RCC ); // инициализация счетчика
LIGHT_ON( LIGHT_PORT, BLUE_LIGHT ); // включить синий свет
TIM10_Start; // запуск счетчика – 5 тактов
TIM10_Stop;
k = TIM10->CNT; // сохранить значение счетчика
n = (uint32_t) k;
DisplayUint32Convertion( DisplayArray, n ); // строка для n
LCD_GLASS_DisplayStrDeci( DisplayArray ); // показать число
while(1); // цикл продолжения в реальном времени
}
В теле главной функции main() выполняется обычная инициализация элементов по аналогии с предыдущей программой FT13 (п. 2.4). Отличие лишь в инициализации счетчика таймера с помощью оператора функции Init_TIMx2_CNT( TIM10, TIM10_RCC ). Определение этой функции находится в подключаемом файле #include "Timer_Init.h". Особенности функции Init_TIMx2_CNT() обсуждаются в следующем разделе (п. 2.5.2).