Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2739
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

Таймеры

299

учетом конкретных микроконтроллеров STM32 и частоты HCLK. К сожалению, код, который он генерирует, не охватывает CubeHAL на момент написания данной главы.

11.2.1.1.Генерация временного отсчета в таймерах расширенного управления

До сих пор мы видели, что все базовые функции таймера конфигурируются через экземпляр структуры TIM_Base_InitTypeDef. Данная структура содержит поле с именем RepetitionCounter, используемое для дальнейшего увеличения периода между двумя последовательными событиями обновления: таймер будет отсчитывать заданное количество раз перед установкой события и вызовом соответствующего прерывания. RepetitionCounter доступен только в таймерах расширенного управления, и это приводит к тому, что формула для вычисления частоты событий обновления становится:

Тактовый сигнал таймера Событие обновления = ( )( )( ) Prescaler +1 Period +1 RepetitionCounter +1

Оставляя RepetitionCounter равным нулю (поведение по умолчанию), мы получаем тот же режим работы, что и у базового таймера.

11.2.2. Использование таймеров в режиме опроса

CubeHAL предоставляет три способа использования таймеров: в режимах опроса, прерываний и DMA. По этой причине HAL предоставляет три различные функции для запуска

таймера: HAL_TIM_Base_Start(), HAL_TIM_Base_Start_IT() и HAL_TIM_Base_Start_DMA(). Идея

режима опроса состоит в том, что регистр счетчика таймера (TIMx->CNT) постоянно доступен для проверки заданного значения. Но нужно соблюдать осторожность при опросе таймера. Например, в Интернете довольно часто можно найти код, подобный следующему:

...

while (1) {

if(__HAL_TIM_GET_COUNTER(&htim) == value)

...

Такой способ опроса таймера совершенно неверен, даже если в некоторых примерах он работает. Но почему?

Таймеры работают независимо от ядра Cortex-M. Таймер может отсчитывать очень быстро, до той же тактовой частоты, что и у ядра ЦПУ. Но проверка счетчика таймера на равенство (то есть, чтобы проверить, равно ли оно заданному значению) требует нескольких ассемблерных инструкций ARM, которые, в свою очередь, требуют нескольких тактовых циклов. Нет никакой гарантии, что ЦПУ обратится к регистру счетчика точно в то же время, когда он достигнет сконфигурированного значения (это происходит только в том случае, если таймер работает очень медленно). Лучший способ – проверить, больше или равно текущее значение счетчика таймера указанного значения, или проверить состояние флага UIF15: в худшем случае мы можем иметь сдвиг во времени

15 Однако для этого необходимо, чтобы таймер был включен в режиме прерываний при помощи функции

HAL_TIM_Base_Start_IT().

Таймеры

300

измерения, но мы не потеряем событие в принципе (только если таймер не работает очень быстро, и мы теряем последующие события, поскольку прерывание маскируется

– то есть флаг UIF все еще установлен до того, как он сбрасывается нами вручную или автоматически HAL).

...

while (1) {

if(__HAL_TIM_GET_FLAG(&htim) == TIM_FLAG_UPDATE) {

// Сброс флага IRQ, иначе мы потеряем другие события

__HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);

...

Однако таймеры являются асинхронными периферийными устройствами, и правильный способ управления событием переполнения/опустошения – использование прерываний. Нет причин не использовать таймер в режиме прерываний, если только таймер не работает очень быстро, и генерирование прерываний через несколько микросекунд (или даже наносекунд) полностью перегрузит микроконтроллер, мешая ему обрабатывать другие команды16.

11.2.3. Использование таймеров в режиме DMA

Таймеры часто запрограммированы для работы в режиме DMA, особенно когда они не используются в качестве генераторов временного отсчета. Этот режим гарантирует, что операции, выполняемые таймером, являются детерминированными и имеют наименьшую возможную задержку, особенно если они выполняются очень быстро. Более того, ядро Cortex-M освобождается от управления таймером, обычно включающем в себя обработку довольно частых ISR, которые могут перегружать процессор. Наконец, в некоторых продвинутых режимах, таких как выходной ШИМ-сигнал, практически невозможно достичь заданных частот переключения без использования таймера в режиме

DMA.

По этим причинам таймеры предлагают до семи запросов к DMA, которые перечислены в таблице 6.

Таблица 6: Запросы к DMA (большинство из них доступны только для таймеров общего назначения и расширенного управления)

Запрос к DMA таймера

Описание

TIM_DMA_UPDATE

Запрос при обновлении (генерируется при событии UEV)

TIM_DMA_CC1

Запрос к DMA цепи Захвата/Сравнения 1

TIM_DMA_CC2

Запрос к DMA цепи Захвата/Сравнения 2

TIM_DMA_CC3

Запрос к DMA цепи Захвата/Сравнения 3

TIM_DMA_CC4

Запрос к DMA цепи Захвата/Сравнения 4

TIM_DMA_COM

Запрос при подключении (Commutation request)

TIM_DMA_TRIGGER

Запрос при запуске (Trigger request)

16 Вспомните, что несмотря на то что обработка исключений в микроконтроллере Cortex-M имеет детерминированную задержку (ядра Cortex-M3/4/7 начинают обслуживать прерывание через 12 тактовых циклов ЦПУ, в то время как Cortex-M0 делает это через 15 тактовых циклов, а Cortex-M0+ через 16 тактовых циклов), она имеет незначительные затраты, которые требуют нескольких наносекунд в «низкоскоростных» микроконтроллерах (например, для микроконтроллера STM32F030, работающего на частоте 48 МГц, прерывание обслуживается примерно за 300 нс). Эти затраты должны быть добавлены к накладным расходам, вводимым HAL во время управления прерываниями, как было показано ранее.

Таймеры

301

Базовые таймеры реализуют только запрос TIM_DMA_UPDATE, поскольку они не имеют входных/выходных I/O. Однако действительно полезно воспользоваться запросом TIMx_UP в тех ситуациях, когда мы хотим выполнять передачи через DMA с учетом времени.

В следующем примере показан другой вариант применения мигающего светодиода, но на этот раз мы используем таймер в режиме DMA для включения/выключения светодиода. Здесь мы собираемся использовать таймер TIM6, запрограммированный для переполнения каждые 500 мс: когда это происходит, таймер генерирует запрос TIM6_UP (который в микроконтроллере STM32F030 связан с третьим каналом DMA1), и следующий элемент буфера передается в регистр GPIOA->ODR через DMA в циклическом режиме, в результате чего LD2 мигает бесконечно.

Прочитайте внимательно

Всемействах STM32 F2/F4/F7/L1/L4 только DMA2 имеет полный доступ к шинной матрице. Это означает, что только те таймеры, запросы которых привязаны к данному контроллеру DMA, могут использоваться для выполнения передач с участием другого периферийного устройства (за исключением внутренней и внешней энергозависимой памяти). По этой причине в данном примере для плат Nucleo на основе микроконтроллеров F2/F4/L1/L4 в качестве генератора временного отсчета используется TIM1.

Вмикроконтроллерах STM32F103R8, STM32F302RB и STM32F334R8, STM32L053R8 и STM32L073RZ запрос TIMx_UP не позволяет запустить передачу между памятью и GPIO периферийного устройства. Так что этот пример недоступен для соответствующих плат Nucleo.

Имя файла: src/main-ex2.c

13int main(void) {

14uint8_t data[] = {0xFF, 0x0};

16HAL_Init();

17Nucleo_BSP_Init();

18MX_DMA_Init();

19

20htim6.Instance = TIM6;

21htim6.Init.Prescaler = 47999; // 48 МГц / 48000 = 1000 Гц

22htim6.Init.Period = 499; // 1000 Гц / 500 = 2 Гц = 0.5 с

23htim6.Init.CounterMode = TIM_COUNTERMODE_UP;

24__HAL_RCC_TIM6_CLK_ENABLE();

25

26HAL_TIM_Base_Init(&htim6);

27HAL_TIM_Base_Start(&htim6);

29hdma_tim6_up.Instance = DMA1_Channel3;

30hdma_tim6_up.Init.Direction = DMA_MEMORY_TO_PERIPH;

31hdma_tim6_up.Init.PeriphInc = DMA_PINC_DISABLE;

32hdma_tim6_up.Init.MemInc = DMA_MINC_ENABLE;

33hdma_tim6_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;

34hdma_tim6_up.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;

Таймеры

302

35hdma_tim6_up.Init.Mode = DMA_CIRCULAR;

36hdma_tim6_up.Init.Priority = DMA_PRIORITY_LOW;

37HAL_DMA_Init(&hdma_tim6_up);

38

39HAL_DMA_Start(&hdma_tim6_up, (uint32_t)data, (uint32_t)&GPIOA->ODR, 2);

40__HAL_TIM_ENABLE_DMA(&htim6, TIM_DMA_UPDATE);

41

42while (1);

43}

Строки [29:37] конфигурируют DMA_HandleTypeDef для DMA1_Channel3 в циклическом режиме. Затем строка 39 начинает передачу через DMA, так что содержимое буфера data передается в регистр GPIOA->ODR каждый раз, когда генерируется запрос TIM6_UP, то есть когда происходит переполнение таймера. Это приводит к тому, что светодиод LD2 мигает. Обратите внимание, что здесь мы не используем функцию

HAL_TIM_Base_Start_DMA(). Почему так?

Рассматривая реализацию процедуры HAL_TIM_Base_Start_DMA(), вы можете увидеть, что инженеры ST определили ее так, чтобы передача через DMA осуществлялась из буфера памяти в TIM6->ARR, который соответствует значению Period.

HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length) {

...

/* Включение канала DMA */

HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_UPDATE], (uint32_t)pData, (uint32_t)&htim \ ->Instance->ARR, Length);

/* Разрешение запроса к DMA при обновлении TIM */

__HAL_TIM_ENABLE_DMA(htim, TIM_DMA_UPDATE);

...

По сути, мы можем использовать HAL_TIM_Base_Start_DMA() только для изменения Period таймера при каждом его переполнении. Поэтому нам нужно сконфигурировать DMA самостоятельно, чтобы выполнить эту передачу.

11.2.4. Остановка таймера

CubeHAL предоставляет три функции для остановки таймера: HAL_TIM_Base_Stop(),

HAL_TIM_Base_Stop_IT() и HAL_TIM_Base_Stop_DMA(). Мы выбираем одну из них в зависи-

мости от режима таймера, который мы используем (например, если мы запустили таймер в режиме прерываний, то нам нужно остановить его с помощью процедуры HAL_TIM_Base_Stop_IT()). Каждая функция предназначена для правильного запрета IRQ и отключения конфигураций DMA.

11.2.5.Использование CubeMX для конфигурации базового таймера

CubeMX может свести к минимуму затрачиваемые усилия, необходимые для конфигурации базового таймера. Если таймер разрешен в представлении Pinout с помощью флажка Activated, его можно сконфигурировать в представлении Configuration.