Автор работы: Пользователь скрыл имя, 09 Сентября 2014 в 09:42, лекция
В микроконтроллерах семейства STM32L1xx можно организовать отсчет времени по тактам системных часов. По умолчанию в момент запуска системные часы ядра CORTEX – 3M устанавливаются на тактовую частоту f=2000000 гц, обеспечивая длительность одного такта τ=1/f=1/2000000 с =0.0000005 с =0.5 млс. Измерители времени или таймеры способны автономно подсчитывать системные такты до заданной величины.
После инициализации макрос 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
//
Количество тактов для
//
#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
// Количество тактов для
//
#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 ); // инициализация счетчика