Таймеры в микроконтроллерах

Автор работы: Пользователь скрыл имя, 09 Сентября 2014 в 09:42, лекция

Краткое описание

В микроконтроллерах семейства STM32L1xx можно организовать отсчет времени по тактам системных часов. По умолчанию в момент запуска системные часы ядра CORTEX – 3M устанавливаются на тактовую частоту f=2000000 гц, обеспечивая длительность одного такта τ=1/f=1/2000000 с =0.0000005 с =0.5 млс. Измерители времени или таймеры способны автономно подсчитывать системные такты до заданной величины.

Прикрепленные файлы: 1 файл

2 Таймеры.docx

— 113.41 Кб (Скачать документ)

После инициализации макрос TIM10_Start запускает счетчик таймера. Следующая инструкция TIM10_Stop останавливает счетчик. Поскольку между стартом и остановкой счетчика нет ни одной инструкции, дисплей должен показывать количество тактов, которые затрачивает таймер на автономию. На дисплее находится число 9. Это количество тактов автономии таймера. На что затрачиваются эти 9 тактов обсуждается далее в п. 2.5.3.

 

 

 

2.5.2. Функции Init_TIMx1_CNT и Init_TIMx2_CNT.

Эти две функции предназначены для инициализации таймеров в режиме измерения количества тактов между стартом и остановкой счетчика. В таких таймерах длина серии должна составлять один такт. Поскольку счетчики содержат 16 битов, то максимально можно измерить только тактов. Затем счетчик будет сброшен в 0. Такие таймеры не нуждаются в инициализации возможности прерываний. Эти функции размещены в подключаемом файле Timer_Init.h.

void Init_TIMx1_CNT( TIM_TypeDef* TIMx, uint32_t TIMx_RCC )

{

  RCC->APB1ENR |= TIMx_RCC;                // включить часы TIMx

  TIMx->ARR = (uint16_t) 0xFFFF;    // число серий на прерывание

  TIMx->PSC = (uint16_t) 1;              // число тактов в серии

}

//--------------------------------------------------------------

void Init_TIMx2_CNT( TIM_TypeDef* TIMx, uint32_t TIMx_RCC )

{

  RCC->APB2ENR |= TIMx_RCC;                // включить часы TIMx

  TIMx->ARR = (uint16_t) 0xFFFF;    // число серий на прерывание

  TIMx->PSC = (uint16_t) 1;              // число тактов в серии

}

 

2.5.3. Ассемблерный код фрагмента программы.

В Приложении Ассемблер изложена технология просмотра ассемблерного кода выполняемой программы. В проекте FT21 нас интересует только фрагмент программы от макроса TIM10_Start до макроса TIM10_Stop, поскольку необходимо ответить на вопрос: “На что же были потрачены 9 тактов между выполнениями этих макросов подряд?”. Ниже приводится ассемблерный код фрагмента программы между инструкциями TIM10_Start; TIM10_Stop из главной функции main(). Ассемблерный текст можно получить, используя дисассемблирование загрузочного кода программы после компиляции исходного текста на языке Си. Такая технология рассматривается в Приложении Ассемблер.

 

 

 

 

 

    26:         TIM10_Start;                  // запуск счетчика 

0x080006D6 4811      LDR      r0,[pc,#68]  ; @0x0800071C

0x080006D8 8800      LDRH     r0,[r0,#0x00]

0x080006DA F0400001  ORR      r0,r0,#0x01

0x080006DE 490F      LDR      r1,[pc,#60]  ; @0x0800071C

0x080006E0 8008      STRH     r0,[r1,#0x00]

    27:         TIM10_Stop;                  // останов счетчика

0x080006E2 4608      MOV      r0,r1

0x080006E4 8800      LDRH     r0,[r0,#0x00]

0x080006E6 F0200001  BIC      r0,r0,#0x01

0x080006EA 8008      STRH     r0,[r1,#0x00]

    28:         k = TIM10->CNT;   // сохранить значение счетчика

 

Напомним, что определение макроса TIM10_Start располагается в подключаемом фале Timer_Init.h в следующем виде:

#define TIM10_Start TIM10->CR1 |= TIM_CR1_CEN.

Из ассемблерного представления инструкции TIM10_Start видно, что на языке Си запуск счетчика TIM10->CR1 |= TIM_CR1_CEN выполняется с помощью пяти машинных команд. По команде LDR r0,[pc,#68] адрес 0x0800071C для volatile-переменной  таймерного регистра TIM10->CR1 загружается в общий регистр r0 – это 1 такт. Используя адрес, записанный в регистре r0, значение таймерного регистра TIM10->CR1 считывается и загружается в этот же общий регистр r0 по команде  LDRH r0,[r0,#0x00] – это 3 такта. Команда ORR r0,r0,#0x01 выполняет дизъюнкцию с константой TIM_CR1_CEN – это 1 такт. Сейчас результат находится в общем регистре r0. Адрес 0x0800071C для volatile-переменной  таймерного регистра  TIM10->CR1 отсутствует в каком-либо общем регистре. По команде LDR r1,[pc,#60] он загружается в регистр r1 – это 1 такт. По команде STRH r0,[r1,#0x00] содержимое регистра r0 копируется в регистр TIM10->CR1, включая этим счетчик TIM10->CNT – это 3 такта.

Таким образом, выполнение макроса TIM10_Start происходит за 1+3+1+1+3=9 машинных тактов. Но счетчик включился только на последней ассемблерной команде STRH r0,[r1,#0x00]. Пока в счетчике TIM10->CNT еще находится 0.

В исходной программе за Си-инструкцией  TIM10_Start непосредственно следует Си-инструкция TIM10_Stop, макрос которой находится в подключаемом фале Timer_Init.h в следующем виде:

#define TIM10_Stop  TIM10->CR1 &= ~TIM_CR1_CEN.

Из ассемблерного представления инструкции TIM10_Stop видно, что на языке Си останов счетчика TIM10->CR1 &= ~TIM_CR1_CEN  выполняется за четыре машинные команды. По команде MOV r0,r1 имеющийся в общем регистре r1 адрес 0x0800071C volatile-переменной регистpа TIM10->CR1 перемещается в общий регистр r0 – это 1 такт. Используя этот адрес в регистре r0, команда LDRH r0,[r0,#0x00] считывает и загружает значение таймерного регистра TIM10->CR1 в этот же общий регистр r0 – это 4 такта. Далее по команде  BIC r0,r0,#0x01 значение в r0 инвертируется и дизъюнктируется с константой 0x01 – это 1 такт. Результат остается в регистре r0. Адрес таймерного регистра TIM10->CR1 все еще находится в общем регистре r1, что позволяет сэкономить одну машинную команду на его загрузку. В последней команде STRH r0,[r1,#0x00] результат запоминается в таймерном регистре  – это 4 такта.

Таким образом, выполнение макроса TIM10_Stop происходит за 1+4+1+4=10 машинных тактов. Однако дисплей показал 9 тактов. Дело в том, что счетчик остановился на один такт раньше, чем закончились 4 такта последней команды STRH r0,[r1,#0x00]. Счетчик подсчитывал такты, пока не сбросили в 0 соответствующий бит таймерного регистра  TIM10->CR1.

 

2.6. Время копирования целой константы 0 в проекте FT22

В представленном ниже проекте FT22 таймерный счетчик используется для определения количества тактов, необходимых для выполнения операции присваивания целой константы 0. Такая константа достаточно универсальна для программирования. С другой стороны, 0 отличается от других констант своей лаконичностью. Кроме того, целые переменные могут иметь размер 1, 2 и 4 байта. Интересно, будет ли изменяться количество тактов, необходимых для копирования нуля в соответствующие переменные? Ответ получим в проекте FT22. Время можно получить, умножив количество тактов на время одного такта. Но мы делать этого не будем, поскольку длительность одного такта зависит от задаваемой системной частоты микроконтроллера каждого конкретного устройства. Подключаемые файлы взяты из предыдущего проекта FT21 (п. 2.5).

// Project FT22

// Количество тактов для копирования  целой константы 0

//                                 // Из учебного курса А. Деона

#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"

//--------------------------------------------------------------

uint8_t  a;                // целая переменная размером в 1 байт

uint16_t b;               // целая переменная размером в 2 байта

uint32_t c = 0;           // целая переменная размером в 4 байта

 

uint32_t t = 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;                                // запуск счетчика

  a = 0;                       // 8 тактов на константу в 1 байт

//  b = 0;                    // 8 тактов на константу в 2 байта

//  c = 0;                    // 8 тактов на константу в 4 байта

  TIM10_Stop;                    // останов счетчика - 10 тактов

  t = TIM10->CNT - 10;   // количество тактов без учета счетчика

  DisplayUint32Convertion( DisplayArray, t );    // строка для t

  LCD_GLASS_DisplayStrDeci( DisplayArray );    // показать число

 

  while(1);               // цикл продолжения в реальном времени

}

Посмотрим, какой ассемблерный код будет соответствовать выполняемым подряд Си-инструкциям TIM10_Start; a = 0; TIM10_Stop.

 

    29:         TIM10_Start;                 // запуск счетчика

0x080006D6 4811      LDR      r0,[pc,#68]  ; @0x0800071C

0x080006D8 8800      LDRH     r0,[r0,#0x00]

0x080006DA F0400001  ORR      r0,r0,#0x01

0x080006DE 490F      LDR      r1,[pc,#60]  ; @0x0800071C

0x080006E0 8008      STRH     r0,[r1,#0x00]

    30:         a = 0;         // 8 тактов на константу в 1 байт

0x080006E2 2000      MOVS     r0,#0x00

0x080006E4 490F      LDR      r1,[pc,#60]  ; @0x08000724

0x080006E6 7008      STRB     r0,[r1,#0x00]

   33:         TIM10_Stop;     // останов счетчика - 10 тактов

0x080006E8 480C      LDR      r0,[pc,#48]  ; @0x0800071C

0x080006EA 8800      LDRH     r0,[r0,#0x00]

0x080006EC F0200001  BIC      r0,r0,#0x01

0x080006F0 490A      LDR      r1,[pc,#40]  ; @0x0800071C

0x080006F2 8008      STRH     r0,[r1,#0x00]

Запуск счетчика TIM10_Start произошел без изменения – 5 машинных команд (п. 2.5.3). На останов счетчика TIM10_Stop требуется 5 машинных команд против прежних 4-х команд (п. 2.5.3). Дополнительная команда LDR r0,[pc,#48]появилась из-за того, что между стартом и остановом счетчика находится Си-инструкция однобайтного присваивания a = 0. Следовательно, остановка счетчика производится за 10 тактов против прежних 9 тактов в проекте FT21 (п. 2.5.3). Именно эти 10 тактов вычитаются из показания счетчика перед отображением на дисплее t = TIM10->CNT – 10. Таким образом, дисплей показывает количество тактов, затрачиваемых программным кодом без учета таймера.

  Копирование однобайтного нуля выполняет Си-инструкция a = 0. Ноль находится непосредственно в самой ассемблерной команде MOVS  r0,#0x00, которая перемещает его в общий регистр r0 – это 1 такт. Команда LDR r1,[pc,#60] загружает адрес переменной a в регистр r1 – это 1 такт. Последняя команда STRB r0,[r1,#0x00] сохраняет один байт регистра r0 в переменной a – это 3 такта.

Дисплей показывает 1+1+3+3=8 тактов. Дополнительные 3 такты потрачены на внутреннюю синхронизацию процессора и шины доступа к переменным.

Теперь посмотрим, сколько машинных команд и тактов необходимо для копирования двухбайтного нуля в Си-инструкции b = 0 в том же проекте FT22, изменив соответствующим образом комментарии в инструкциях между TIM10_start и TIM10_stop.

    31:         b = 0;        // 8 тактов на константу в 2 байта

0x080006E2 2000      MOVS     r0,#0x00

0x080006E4 490F      LDR      r1,[pc,#60]  ; @0x08000724

0x080006E6 8008      STRH     r0,[r1,#0x00]

Этот код совпадает с кодом для предыдущей инструкции a = 0. По-прежнему команда  MOVS r0,#0x00 перемещает однобайтный 0 в общий регистр r0, сбрасывая остальные биты этого регистра тоже в 0. Но команда STRH r0,[r1,#0x00] сохраняет два байта в переменной b – это 3 такта.

Дисплей показывает 1+1+3+3=8 тактов. Дополнительные 3 такты потрачены на внутреннюю синхронизацию процессора и шины доступа к переменным.

Копирование четырехбайтного нуля производится в Си-инструкции c = 0. Соответствующий ассемблерный код представлен ниже.

    32:         c = 0;        // 8 тактов на константу в 4 байта

0x080006E2 2000      MOVS     r0,#0x00

0x080006E4 490F      LDR      r1,[pc,#60]  ; @0x08000724

0x080006E6 6008      STR      r0,[r1,#0x00]

Опять мы видим похожую картину. Команда MOVS r0,#0x00 перемещает однобайтный 0 в общий регистр r0, сбрасывая остальные биты этого регистра тоже в 0. А команда STR r0,[r1,#0x00] записывает 32 нулевых бита по 32-хбитной шине в четырехбайтную переменную c – это по-прежнему 3 такта.

Дисплей показывает 1+1+3+3=8 тактов. Дополнительные 3 такты потрачены на внутреннюю синхронизацию процессора и шины доступа к переменным.

Итак, во всех трех случаях использовалось по 8 тактов, поскольку регистр r0 содержит 32 бита, и копирование нуля производится по 32-хбитной шине. Отличие только в том, сколько бит сохраняется в переменной.

 

2.7. Время копирования вещественных констант 0.0F и 0.0 в проекте FT23.

В представленном ниже проекте FT23 подсчитывается количество системных тактов, необходимых для копирования четырехбайтного вещественного нуля 0.0F и восьмибайтного вещественного нуля 0.0 в нотации языка Си.

// Project FT23

// Количество тактов для копирования  вещественной константы 0.0

//                                 // Из учебного курса А. Деона

#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"

//--------------------------------------------------------------

float a;           // вещественная переменная размером в 4 байта

double b;           // вещественная переменная размером в 8 байт

 

uint32_t t = 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 );  // инициализация счетчика

Информация о работе Таймеры в микроконтроллерах