Таймеры stm32 HAL - часть первая





Вторая часть тут.


Таймеры — это самая крутая штуковина, которая есть у микроконтроллеров. С их помощью можно замерять время, длину импульсов, считать импульсы, создавать ШИМ-сигнал, запускать один таймер другим, запускать таймером некоторую периферию, запускать ракеты в космос и т.д. А самая круть заключается в том, что всё это происходит на аппаратном уровне, то есть без участия CPU.

Я уже писал статью про таймеры stm32, применительно к IDE Arduino, там простым языком рассказано, как работает таймер, что такое предделитель, переполнение и захват/сравнение. Мне не хочется полностью дублировать эту информацию здесь, поэтому если Вы не знакомы с таймерами или знакомы поверхностно, то прочтите вначале это. Примеры не смотрите, они для IDE Arduino.

Таймеры stm32 делятся на три вида:

Advanced-control timers (TIM1 и TIM8) — самые «нафаршированные».
General-purpose timers (TIM2, TIM3, TIM4, TIM5) — функционал чуть меньше чем у предыдущих.
Basic timers (все остальные) — сильно урезанный функционал.


Описание сделано на примере таймера №1.




Начнём с самого простого — выполнение какого-нибудь действия через определённые промежутки времени. Таймер будет каждые 500мс генерировать прерывание и мигать светодиодом в обработчике.

На всякий случай:
мс — миллисекунда (1000мс = 1сек).
мкс — микросекунда (1000000мкс = 1сек).
нс — наносекунда (1000000000нс = 1сек)



Первым делом нужно выбрать источник тактирования…



Clock Source ⇨ Internal Clock — в данном случае источник будет внутренний — частота от шины APB2…


особенность
Если для тактирования шины APB2 используется делитель больше 1, то тактирование таймеров происходит с частотой в 2 раза больше частоты тактового сигнала в шине. То есть, если для шины установить делитель /2, то у таймера появится множитель X2…





Далее настраиваем таймер…




Prescaler
Prescalerпредделитель системной частоты таймера (частоты поступающей с шины APB2).

Предделитель это цифровой счётчик, который считает между тиками таймера. То есть, допустим мы установили предделитель равным какому-то значению, тогда между каждым тиком таймера, счётчик предделителя будет со скоростью системной частоты таймера отсчитывать от 0 до этого значения и обнуляться. При каждом обнулении счётчика, таймер будет совершать очередной тик. Таким образом происходит деление частоты.

Поскольку в цифровых счётчиках отсчёт начинается с нуля (0, 1, 2, 3...), то предделитель нужно указывать с вычетом единички. При значении Prescaler = 0, коэфф.деления = 1:1. При Prescaler = 1, коэфф.деления = 1:2.
То есть, чтобы разделить частоту на 2, нужно в Prescaler записать 1, чтобы разделить частоту на 3, нужно в Prescaler записать 2, и т.д. То есть в нашем случае чтоб получить частоту 100кГц нужно указать Prescaler 719.

В примерах я округляю значение для удобства восприятия информации, но в рабочих проектах обязательно вычитайте единичку.

Counter Mode
Up — счётчик будет считать «вверх», от нуля до значения переполнения.

Down — счётчик будет считать «вниз», от значения переполнения до нуля.

Режимы Center Aligned описаны в разделе про ШИМ.

Counter Period
Counter Periodпереполнение. Счётчик будет считать до 50000. При частоте 100кГц он досчитает до 50000 за 500мс.

Здесь такая же ситуация как и с Prescaler'ом, нужно вычитать единичку из значения. То есть вместо 50000, нужно писать 49999.

Internal Clock Division
Делитель системной частоты таймера для формирования вспомогательного тактового сигнала, который используется в цифровых фильтрах, и для формирования времени запаздывания при работе в режиме PWM с комплементарными выходами (dead-time). Смотрите пункт про цифровой фильтр.

Repetition Counter
Если указать число больше нуля, то прерывание будет генерироваться не при каждом переполнение счётчика, а на каждые N переполнений. Например, если указываем 4, то прерывание будет вызываться при каждом четвёртом переполнении.

Так же смотрите в конце раздел «N-pulse waveform generation».

auto-reload preload
У некоторых регистров таймера есть дублирующие регистры, основной регистр называется «теневым» (на схема его рисуют с тенью, доступ из программы к нему невозможен), а дублирующий называется буферным регистром. Запись в теневой регистр производится через буферный — записали новое значение в буферный регистр, а оттуда оно переносится в теневой.


Регистр ARR — Auto-reload register (в нём хранится значение переполнения, в нашем случае это — 50000).

Когда мы изменяем значение переполнения, то новое значение записывается в буферный регистр, а дальше происходит следующее: если указано Disable, то новое значение тут же переносится в теневой регистр, а если указать Enable, то новое значение перенесётся в теневой регистр только после того, как таймер переполнится. Таким образом можно защитится от несвоевременного изменения значения, когда это значение нужно менять по ходу программы.


Теперь включите прерывание…


TIM1 update interrupt — прерывание при переполнении. Счётчик переполнился и обновился (update).

Перед бесконечным циклом запустим таймер в режиме прерывания:

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1);
/* USER CODE END 2 */


В колбеке мигаем светиком:

/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM1) //check if the interrupt comes from TIM1
	{
		HAL_GPIO_TogglePin(led13_GPIO_Port, led13_Pin); 
	}
}


Это самое распространённое использование таймера — выполнение каких-либо действий через определённые интервалы времени.


Важное замечание
Сначала коротко о NVIC (Nested Vector Interrupt Controller) — контроллер вложенных векторных прерываний. NVIC устроен так, что прерывание происходит не само по себе, а его вызывает аппаратное событиеEvent. То есть когда например таймер переполняется, происходит Event обновления (Update event), и если разрешено прерывание, тогда Event «приказывает» этому прерыванию произойти. Если прерывание не разрешено, то Event в любом случае происходит, но прерывание не вызывается.

Так вот, о чём это я, а да… Когда вы инициализируете таймер и записываете значения в Prescaler и в Repetition counter, то они применяются не сразу, а только после того как таймер отработает цикл и произойдёт Update event. Так устроена система.

Пока новые значения не применились, мы не можем знать что там записано, может ноль, а может какой-то мусор. Следовательно мы не знаем когда произойдёт первое прерывание, и произойдёт ли вообще, и будет ли работать таймер. Поэтому сразу после инициализации нужно принудительно сгенерировать (записать в нужный регистр соответствующий бит) этот самый Update event. Таким образом наши новые значения будут применены при инициализации и всё будет хорошо.

Библиотека HAL делает это так…


Файл stm32f1xx_hal_tim.c.

В регистр EGR записывается бит UG (Update Generation).

Теперь смотрите что происходит если мы потом запускаем таймер в режиме прерываний — HAL_TIM_Base_Start_IT(&htim1), а происходит следующее: поскольку прерывания вызываются событиями, то у нас тут же произойдёт прерывание от таймера так как событие мы уже сгенерировали принудительно, и оно только и ждало когда же запустится таймер чтоб сообщить ему что нужно сделать прерывание. Таким образом получается, что первое прерывание от таймера произойдёт не через указанный нами период, а сразу после старта системы. В каких-то случаях, как в примере с миганием светиком, это не критично, а в каких-то это может оказаться неприемлемым.

Исправить эту ситуацию очень просто, надо всего лишь очистить флаг перед запуском таймера, вот так…

__HAL_TIM_CLEAR_FLAG(&htim1, TIM_SR_UIF); // очищаем флаг
HAL_TIM_Base_Start_IT(&htim1);

Теперь прерывание произойдёт именно тогда когда нам нужно.

Если в процессе работы вам нужно будет остановить таймер и снова запустить, то ничего очищать не нужно.




Следующим источником тактирования идёт ETR2. Если указать ETR2, тогда источником будет служить какой-то внешний сигнал.

В кубе делаем так…



Источник тактирования — ETR2, предделитель не используем — 0, переполнение будет 10. Раздел Trigger Output пока не нужен, а про Clock под спойлером…

ETR2 и цифровой фильтр
ETR2 — это режим — External clock source mode 2

ETR1 описан во второй части.

В режиме ETR2 таймер тактируется нарастающими фронтами (с LOW на HIGH) частоты подаваемой на пин TIM1_ETR (PA12).

Входящий сигнал проходит несколько этапов...




Настройки раздела Counter Settings такие же как и при Internal Clock, а вот раздел Clock следует изучить…



Clock Filter — это фильтр входящего сигнала, с помощью которого можно отсеивать шум/дребезг в линии. Это офигенно круто, так как всё делается аппаратно (см. ниже).

Clock Polarity — если инвертировать, то таймер будет тактироваться спадающим сигналом (с HIGH на LOW).

Clock Prescaler — предделитель частоты входящего сигнала. Если входящая частота очень высокая, то её нужно понизить этим предделителем таким образом, чтоб она получилась меньше системной частоты таймера по крайней мере в три раза. То есть, если системная частота таймера 72мГц, то входящая частота после выхода с этого предделителя должна быть не выше 24мГц. То есть, допустим, входящая частота у нас 50мГц, тогда применяем делитель 4 и получаем приемлемую частоту 12.5мГц.

Это может показаться непонятным однако объясняется всё очень просто. Микроконтроллер не может просто так взять и подсчитать какую-то частоту, любая входящая частота должна быть синхронизирована с внутренней (системной) частотой, а для этого, согласно теореме Котельникова, частота дискретизации должна более чем в два раза превышать входящую. Выглядит это так…


Входящая частота (допустим 50мГц) приходит на ETR-пин, попадает в асинхронный (не зависящий от системной частоты) предделитель Divider (он же Clock Prescaler), делится там на четыре, попадает в блок Filter downcounter где выполняется дискретизация (сэмплирование — см. ниже) этого сигнала с помощью системной частоты — fDTS, то есть микроконтроллер определяет эту частоту.

При дальнейшей работе с этой частотой в своей программе нужно учитывать что она разделена на четыре.

General-purpose timer cookbook (AN4776) стр. 22.


Как работает фильтр

Входящий сигнал сэмплируется системной частотой таймера, то есть выполняются выборки (проверки, или прощупывание) входного сигнала (см. картинку ниже). Если в течении заданного количества выборок сигнал был стабилен, то всё окей, если же он переключился раньше чем закончились выборки, то это расценивается как дребезг и отвергается.

Частота и количество сэмплов (выборок) задаётся цифрами от 0 до 15, которые соответствуют следующим фильтрам…

0 фильтр не используется.
1 fSAMPLING=fCK_INT, N=2.
2 fSAMPLING=fCK_INT, N=4.
3 fSAMPLING=fCK_INT, N=8.
4 fSAMPLING=fDTS/2, N=6.
5 fSAMPLING=fDTS/2, N=8.
6 fSAMPLING=fDTS/4, N=6.
7 fSAMPLING=fDTS/4, N=8.
8 fSAMPLING=fDTS/8, N=6.
9 fSAMPLING=fDTS/8, N=8.
10 fSAMPLING=fDTS/16, N=5.
11 fSAMPLING=fDTS/16, N=6.
12 fSAMPLING=fDTS/16, N=8.
13 fSAMPLING=fDTS/32, N=5.
14 fSAMPLING=fDTS/32, N=6.
15 fSAMPLING=fDTS/32, N=8.

Такие фильтры используются не только при тактировании, но и при подсчёте поступающих импульсов, измерении длины импульсов, и ещё много где.

Ниже представлен пример из AN4776, глава 1.4.1, где к входящему сигналу (просто какой-то сигнал) применяется фильтр №4 — fSAMPLING=fDTS/2, N=6.



TCK_INT
Опорной частотой для сэмплирования служит системная частота таймера (шина APB2), вне зависимости от того, как тактируется таймер. Шина работает всегда.

TDTS
Это тот самый вспомогательный тактовый сигнал, про который упоминалось выше.
Частота TDTS получается после того, как частота TCK_INT делится делителем Internal Clock Division (CKD) на два. Если CKD не используется, то TDTS будет равна TCK_INT.

CKD применяется в том случае, если нужно увеличить время исследования сигнала — частота сэмплов уменьшится, следовательно проверка будет происходить дольше.


TSAMPLING
Применяя фильтр fSAMPLING=fDTS/2, N=6 к частоте TDTS мы делим её на два, и получаем частоту сэмплирования — TSAMPLING (fSAMPLING) в четыре раза меньше системной частоты таймера. Количество сэмплов будет 6 (N=6).

В итоге к входящему сигналу (input signal) будет применено шесть выборок (123456). Если на протяжении этого времени сигнал не менял полярность, то всё окей. Любой сигнал с меньшей длительностью будет отклонён.

Так как все шесть выборок были успешные, отфильтрованный сигнал (Filtred signal) переключился в HIGH. Как видно, переключение отфильтрованных сигналов будет происходить с небольшим сдвигом во времени, равным времени выборок.

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


Не забудьте включить прерывание — update interrupt.


Сигнал на таймер будем подавать с GPIO PA5, настройте его как GPIO_Output. В программе остаётся прежний колбек со светиком, а в бесконечном цикле создаём тактирующую частоту для таймера — 10Гц.

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1);
/* USER CODE END 2 */


/* USER CODE BEGIN WHILE */
  while (1)
  {
	  HAL_GPIO_WritePin(pa5_GPIO_Port, pa5_Pin, GPIO_PIN_RESET);
	  HAL_Delay(50);
	  HAL_GPIO_WritePin(pa5_GPIO_Port, pa5_Pin, GPIO_PIN_SET);
	  HAL_Delay(50);
          ...

Соединяем PA5 с PA12 (TIM1_ETR) и прошиваем. Таймер будет тактироваться с частотой 10Гц, счётчик переполняется на значении 10 и генерирует прерывание — светик переключается раз в секунду.

Если хотите, можете использовать…
фильтр
Сигнал у нас очень долгий (50мс), поэтому можно смело указать 15 (fSAMPLING=fDTS/32, N=8).

Делитель Internal Clock Division отключён, значит вспомогательный тактовый сигнал (fDTS) будет равен системной частоте, значит частота выборок (fSAMPLING) будет равна — 72000000 / 32 = 2250000 выборок в секунду, значит на восемь выборок будет затрачено 3.5 мкс (8 / 2250000 = 0.0000035 = 3.5 мкс). Если воспользоваться делителем Internal Clock Division (CKD), то можно увеличить время выборок в два или четыре раза...





Так же этот режим можно использовать для подсчёта импульсов входящей частоты. Подробно это описано в статье про частотомер.


Про другие источники и способы тактирования таймера во второй части.




Теперь давайте познакомимся с каналами таймера.

У каждого таймера есть четыре независимых канала, которые могут подключаться к физическим пинам микроконтроллера, то есть работать как внешние входы/выходы, а могут и не подключаться, и работать как внутренние входы/выходы для взаимодействия с другими таймерами. У некоторых МК, например у F303, есть доп. каналы (5 и 6) не имеющие выхода наружу.

Поэкспериментируем с первым каналом в режиме выхода — Output Compare. В этом режиме можно устанавливать уровень (HIGH или LOW) на выводной пин. Уровень переключается в момент сравнения. В этот момент можно генерировать прерывание.

Удалите все прежние настройки таймера и сделайте так…


Указываем режим Output Compare для первого канала. Если в этом режиме не указывать источник тактирования, то по умолчанию используется системная частота таймера.



Здесь устанавливаем предделитель 7200, а переполнение 50000. Таким образом переполнение будет происходить каждые 5 сек.




Mode ⇨ Toggle on match — в момент сравнения, на пине PA8 (TIM1_CH1) будет происходить смена уровня с HIGH на LOW и наоборот. В данном режиме не важно какое указано сравнение, главное чтоб оно было больше нуля и меньше (или равным) переполнения (50000).

другие варианты
Frozen — в момент сравнения состояние выхода не изменяется. Таймер в этом режиме может использоваться для того, чтобы формировать интервалы времени в программе (прерывания по сравнению).

Active Level on match — в момент сравнения на выход подаётся HIGH и больше не изменяется.

Inactive Level on match — в момент сравнения на выход подаётся LOW и больше не изменяется.

Forced Active — на выход сразу же (независимо от значений переполнения и сравнения) подаётся HIGH и больше не изменяется.

Forced Inactive — на выход сразу же (независимо от значений переполнения и сравнения) подаётся LOW и больше не изменяется.


Pulse — здесь вписывается значение сравнения. В данном случае прерывание по сравнению будет происходить когда счётчик досчитает до 25000.

Сейчас сгенерируйте проект, подключите к PA8 светодиод, а в коде впишите (перед бесконечным циклом) команду…

/* USER CODE BEGIN 2 */
HAL_TIM_OC_Start_IT(&htim1, TIM_CHANNEL_1); // генерирует прерывание
//HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1); // не генерирует прерывание
/* USER CODE END 2 */

Таймер запускается в режиме Output Compare для канала №1.

Прошейте и нажмите кнопочку Reset. Светодиод загорится через 2.5 секунды, то есть после того как счётчик досчитает до сравнения. Если же изменить полярность сигнала — CH Polarity ⇨ Low, то после Reset'а светик загорится сразу же, а погаснет через 2.5 секунды. То есть этот пункт позволяет выбрать — когда будет устанавливаться высокий уровень на линии, до сравнения или после.

CH Idle State — определяет логический уровень сигнала на выходе канала в состоянии ожидания (пока не трогайте).


Если нужно чтоб в момент сравнения генерировалось прерывание, то его нужно активировать…



А в программу добавить колбек…

void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM1)
	{
		HAL_GPIO_TogglePin(led13_GPIO_Port, led13_Pin);
		snprintf(trans_str, 31, "Compare %lu\n", __HAL_TIM_GET_COUNTER(&htim1));
		HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
	}
}






Комплементарные выходы.

У каналов настроенных на выход есть дополнительные (комплементарные) выходы, которые выдают инвертированный сигнал…


Такие возможности есть не у всех каналов, только у первых трёх.

В режиме Output Compare CH1 CH1N будут активированы два пина — PA8 (TIM1_CH1) и PA7 (TIM1_CH1N). Когда на один пин подаётся сигнал HIGH, то на второй подаётся LOW. Буква N как бы намекает на negative (обратный)...




Для старта комплементарного канала есть своя команда — HAL_TIMEx_OCN_Start_IT(). Запуск обоих каналов выглядит так…

/* USER CODE BEGIN 2 */
HAL_TIM_OC_Start_IT(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_OCN_Start_IT(&htim1, TIM_CHANNEL_1);
/* USER CODE END 2 */

Комплементарные каналы описаны в файле — stm32f1xx_hal_tim_ex.c.


Комплементарный канал может работать самостоятельно…



Запуск:

/* USER CODE BEGIN 2 */
HAL_TIMEx_OCN_Start_IT(&htim1, TIM_CHANNEL_1);
/* USER CODE END 2 */



Помимо этого, можно настроить канал без вывода на физический пин…


В этом режиме можно генерировать прерывание (и другие события) по сравнению.

Запуск:

/* USER CODE BEGIN 2 */
HAL_TIM_OC_Start_IT(&htim1, TIM_CHANNEL_1);
/* USER CODE END 2 */


Колбек:

void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM1)
	{
		HAL_GPIO_TogglePin(led13_GPIO_Port, led13_Pin);
	}
}



Последний пункт — Forced Output CHx



На выход сразу же (независимо от значений переполнения и сравнения) подаётся сигнал указанный в пункте Mode (HIGH или LOW)



Прерывания по сравнению продолжают работать. Запуск и колбек такие же как и у режима выше.




ШИМ (PWR) — это разновидность режима сравнения.

Про ШИМ в режиме DMA читайте здесь (потом, попозже).

Удалите всё что настроено и сделайте так…


Указываем режим PWM (ШИМ) для первого канала. Если в этом режиме не указывать источник тактирования, то по умолчанию используется системная частота таймера.



Предделитель по нулям, пущай жарит на всю катушку, а переполнение делаем 60000.




Pulse — в режиме ШИМ, сравнением мы устанавливаем коэффициент заполнения, то есть продолжительность высокого уровня (длина импульса). В данном случае коэффициент заполнения будет 50%. Если указать сравнение 15000, то коэффициент заполнения будет 25%.

CH Polarity ⇨ High — полярность сигнала. Если в режиме Output Compare при значении High светодиод загорался после перехода через сравнение, то в режиме PWR Generation всё наоборот, сначала подаётся высокий уровень, а после перехода через сравнение низкий. Полярность сигнала можно менять не только в этом пункте, но и в пункте Mode ⇨ PWM mode (см. ниже).


Таким образом у нас получается: период — 60000, а длина импульса — 30000. То есть мы получим вот такой ШИМ…

tдлина импульса.
Tпериод.

Период будет равен ~840мкс (на частоте 72мГц один тик таймера выполняется за ~14нс, значит 14 * 60000 = 840000нс = 840мкс).



Counter Mode ⇨ Center Aligned
Режимы Up и Down описаны в начале статьи.

Следующие три пункта связаны с режимом, который называется «выравнивание по центру». В этом режиме счетчик считает от 0 до значения переполнения, а затем считает в обратную сторону (от переполнения до нуля), и т.д. То есть считает туда-сюда.

Если сделать переполнение равное 5, а сравнение 1, то ШИМ будет выглядеть так…

Выравнивание по краю (обычный режим):



Выравнивание по центру:


В отличии от выравнивания по краю, при выравнивании по центру длина импульса и период увеличиваются в два раза.


Center Aligned mode 1 — прерывание по сравнению происходит только когда счётчик считает «вниз».




Center Aligned mode 2 — прерывание по сравнению происходит только когда счётчик считает «вверх».




Center Aligned mode 3 — прерывание по сравнению происходит в обоих случаях.





Когда я пишу что произойдёт прерывание, то подразумевается, что оно произойдёт если оно запрограммировано. То есть, чтоб не писать что-то типа — «в зависимости от настроек может произойти событие которое может вызвать прерывание, бла-бла-бла».

Mode ⇨ PWM mode
PWM mode 1 — вне зависимости от того как считает счётчик («вверх» или «вниз»), канал активен (в состоянии HIGH) пока CNT < CCRx.




PWM mode 2 — вне зависимости от того как считает счётчик («вверх» или «вниз»), канал активен (в состоянии HIGH) когда CNT > CCRx.



Fast Mode
Если включить, то активный фронт триггерного сигнала будет воздействовать на выходной канал как срабатывание схемы сравнения. В результате на выходе устанавливается соответствующий совпадению уровень, независимо от результата сравнения. Благодаря этому задержка от выборки триггерного сигнала до воздействия на выходной канал сокращается до 3 тактов. Подробнее об этом во второй части.


В программе добавьте запуск ШИМа:

/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
/* USER CODE END 2 */

Светик будет гореть в полнакала.


Чтобы светик плавно разгорался и затухал, надо сделать так…

/* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
  uint16_t i = 0;
  volatile uint16_t j = 0;
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  for(i = 0; i <= 60000; i++)
	  {
		  TIM1->CCR1 = i;
		  for(j = 0; j < 100; j++) __NOP();
	  }

	  HAL_Delay(500);

	  for(i = 60000; i > 0; i--)
	  {
		  TIM1->CCR1 = i;
		  for(j = 0; j < 100; j++) __NOP();
	  }

	  HAL_Delay(500);
          ...

Здесь ведётся работа напрямую с регистром сравнения первого канала (CCR1), увеличивая, а потом уменьшая его значение. То есть мы плавно меняем значение


Вместо работы с регистрами можно воспользоваться HALовскими макросами, например, в данном случае можно заменить строчки TIM1->CCR1 = i; на макрос __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, i);

Различные макросы можно посмотреть в файле stm32f1xx_hal_tim.h


Чтобы генерить прерывание в конце импульса, надо его включить…


То же самое что и в режиме Output Compare.

И ловить в колбеке…

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM1)
	{
		HAL_GPIO_TogglePin(led13_GPIO_Port, led13_Pin);
	}
}



Комплементарный ШИМ.



Канал работает с двумя выходами, обычным и комплементарным.



Переполнение оставьте прежним, сравнение (Pulse) укажите — 2000, ну и всё остальное тоже должно быть как на картинке.


Подключите к пину PA7 (TIM1_CH1N) ещё один светодиод, сгенерируйте проект, а в код добавьте запуск комплементарного канала…

/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
/* USER CODE END 2 */

Светики будут гореть с разным накалом. Если в бесконечном цикле ничего не убирали, то светики будут разгораться и угасать в противофазе.

Если сделать так…

… то светики будут работать синхронно.


Комплементарную схему удобно использовать для управления всякими электродвигателями и прочим силовым оборудованием…


Те, кто этим занимается, знает больше меня, поэтому ничего добавлять не буду.


Продвинутые таймеры (TIM1 и TIM8) оснащены механизмом Break (защитное отключение выходных каналов таймера), который приводится в действие по сигналу от внешнего входа (TIMx_BKIN) или в случае обнаружения сбоев в системе тактирования микроконтроллера. С помощью этого механизма можно мгновенно перевести каналы в заранее определённое состояние. При этом сам по себе таймер продолжает работать.

Конфигурирование механизма Break…



BRK Configuration
У микроконтроллеров stm32 есть штуковина под названием CSS (Clock Security System). Это детектор частоты, который при ее сбое сразу же отключает HSE (внешний кварцевый резонатор), включает HSI (внутренний генератор), устанавливает его источником тактирования, посылает сигнал ошибки таймерам и генерирует прерывание извещающее программу о сбое.

Включить систему можно в Кубе, нажав кнопочку Enable CSS…



Прерывание вызывает вот такой колбек…

void HAL_RCC_CSSCallback(void)
{
	HAL_GPIO_WritePin(led13_GPIO_Port, led13_Pin, GPIO_PIN_RESET);
}

Прерывание это немаскируемое, то есть произойдёт полюбасу. Активировать его нигде не нужно.


Так вот, к чему я это всё. Предположим что наш микроконтроллер управляет каким-то силовым оборудованием, и вдруг отваливается кварц, таймер зависает, а на силовые ключи подаётся совсем не тот сигнал который нужен — последствия будут непредсказуемые.

BRK State — если указать Enable, то в момент сбоя тактирования таймеру будет послана команда Break. Получив эту команду таймер тут же установит на выходных каналах ШИМа низкий уровень (LOW), то есть всё отключится и не успеет набедокурить.

Если изменить полярность обоих каналов с High на Low…



Тогда после сигнала break на выходах будет установлен высокий уровень (HIGH).


Помимо этого, таймеру можно «вручную» послать сигнал Break на специальный пин TIM1_BKIN (PA6), который нужно предварительно активировать…


У F303 два пина BKIN и фильтр входного сигнала описанный в начале статьи.


BRK Polarity — выбор логического уровня, который нужно подать на пин TIM1_BKIN чтобы брейкнуть таймер. Когда будете экспериментировать, подтягивайте пин резюком (внутренняя подтяжка очень слабенькая, срабатывает от пальца).


Примечание. У меня на плате BluePill этот пин срабатывал даже без активации — трогаешь его пальцем и всё. Не знаю почему так, может в МК что-то подгорело, может ещё что-то. В общем проверяйте это на своей плате и решайте что делать. Думается что если включён BRK State ⇨ Enable, то независимо от того активирован пин или нет, его нужно подтягивать «железно». На других платах проверять это я поленился.

Если хотите использовать PA6 для других целей, то нужно переназначить TIM1_BKIN на другую ножку (на F103 он ремапится на PB12).

Чтобы посмотреть ремапинг пинов в Кубе, нужно зажать Ctrl и клацнуть пин.




Output Configuration
Automatic Output State — если включить этот пункт, тогда при подаче сигнала Break, каналы будут отключены до тех пока этот сигнал подаётся. Как только сигнал будет снят, работа восстановится.


Off State Selection for Run Mode — выбор отключённого состояния в рабочем режиме.

Disable — на выходах устанавливается логический ноль.
Enable — на выходах устанавливается уровень соответствующий биту полярности.


Off State Selection for Idle Mode — выбор отключённого состояния для режима ожидания.

Disable — на выходах устанавливается логический ноль.
Enable — на выходах устанавливается уровень соответствующий биту состояния ожидания.


У меня нет уверенности в том, что я правильно понимаю эти два пункта (Off State), так что воздержусь от комментариев.


Lock Configuration — позволяет заблокировать для записи отдельные биты некоторых регистров для защиты от перезаписи при программных ошибках. После установки блокировки её нельзя изменить пока не будет нажат Reset.

В таблице показаны в каких регистрах и какие биты будут заблокированы при различных уровнях:



Dead Time — пауза между переключениями комплементарных выходов. Нужна для исключения сквозных токов при управлении силовыми ключами полу/полно мостовых схем…



У Dead Time есть вспомогательная частота (как у цифрового фильтра), которая равна системной частоте таймера если не применяется Internal Clock Division (CKD). В качестве паузы (dead time), в Кубе указывается количество тиков этой частоты — от 0 до 255. Если указать 20, тогда, если системна частота таймера 72мГц, то время Dead Time будет равно 284нс (1 тик выполняется за 14.2нс, 14.2 * 20 = 284нс).


CH Idle State — опять же не совсем понимаю этот пункт. Если один канал сделать Set, то при остановке таймера он будет установлен в HIGH. Если оба канала сделать Set, то при остановке они оба устанавливаются в LOW.



Чтобы Break генерил прерывание, нужно его включить…



Перед бесконечным циклом активировать…

__HAL_TIM_ENABLE_IT(&htim1, TIM_IT_BREAK);


И добавить колбек…

void HAL_TIMEx_BreakCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM1) 
	{
		HAL_GPIO_WritePin(led13_GPIO_Port, led13_Pin, GPIO_PIN_RESET);
	}
}





Use ETR as Clearing Source



У канала настроенного на выход и выдающего сигнал Output Compare или PWM Generation, есть опорный сигнал, который называется OC1REF (цифра определяет номер канала). На схеме это выглядит так…


Полностью схема таймера будет рассматриваться во второй части.

Сигнал OC1REF проходя жёлтый квадратик (это генератор dead time) расщепляется на два сигнала — прямой и инвертированный, а потом эти сигналы проходя через механизм включения/отключения каналов (голубенький квадратик) попадают на пины микроконтроллера.

Так вот, если включить ETR as Clearing Source, и на пин TIM1_ETR (PA12) подать кратковременный сигнал HIGH, то OC1REF тут же переключится в LOW, и будет оставаться таким до тех пор пока таймер не переполнится, то есть пока не произойдёт событие обновления. Если не убирать сигнал ETR, то OC1REF будет оставаться в LOW.



Clear Input



Clear Input Polarity — выбор полярности сигнала ETR.

Clear Input Prescaler — то же самое, что и Clock Prescaler для цифрового фильтра (см. в начале статьи).

Clear Input Filter — фильтр.

Clear Input Chanel x — выбор канала, на который будет воздействовать ETR. Если работают несколько каналов, то все они будут здесь перечислены.


ETR as Clearing Source работает только в режимах Output Compare и/или PWM Generation. Ну и само собой нет возможности включить внешнее тактирование таймера (ETR2).


Когда будете экспериментировать, то можно воспользоваться предыдущим примером, только укажите предделитель 7200, а сравнение 20000, чтоб всё работало медленно и было понятно как срабатывает очистка. Светик на прямом выходе будет гаснуть, а на инвертированном загораться.

TIM1_ETR нужно подключать к «земле» напрямую, сигнал срабатывает когда отсоединяешь от «земли» (когда повиснет в воздухе), даже «плюс» можно не подавать. Во всяком случае так работает у меня на двух разных платах. То есть, на сколько я понимаю, нужно чтоб сигнал был сильный, не через подтягивающие резисторы.





One Pulse — режим одиночного импульса. Таймер досчитает до переполнения и остановиться.

Исправлено и дополнено 15.11.19.

Подача единичного импульса на примере ШИМ…


ARR — регистр переполнения.
CCRx — регистр захвата/сравнения (х — номер канала).
OCx — выходной импульс.



Для примера настроим четвёртый таймер…





Запускаем таймер в одноимпульсном режиме ШИМ…

/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2);
/* USER CODE END 2 */


Подключите светик к пину PB7 и прошейтесь. Поскольку у нас настроен режим PWM mode 2, то светик загорится после того как счётчик перевалит через значение сравнения. То есть светик загорится через две секунды после старта, а когда счётчик достигнет переполнения светик погаснет, а таймер остановится.

Если указать PWM mode 1, то светик загорится сразу при старте, потом погаснет (после того как счётчик перевалит через сравнение), а после того как счётчик переполнится светик загорится снова и уже не погаснет. Таймер опять же остановится.

В одноимпульсном режиме можно запускать таймер в различных режимах (запустить таймер как счётчик, или как Output Compare, и т.д). Важно! Чтоб таймер остановился, он должен обязательно досчитать до переполнения. Если он не досчитает, например вы будете где-то в середине счёта сбрасывать счётчик на ноль, то таймер будет продолжать работать.

Само собой всё это хозяйство может генерить прерывания по сравнению и переполнению. Включите их в Кубе (у четвёртого таймера оно одно на всё) и добавьте к командам окончание _IT.

В прерывании или где-нибудь в программе можно перезапускать импульс командой HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2), то есть нужно просто перезапустить таймер.


Важное дополнение. После очередного обновления библиотеки HAL, появилась необходимость вызова функции остановки таймера после того как он отработает. То есть, когда вы запускаете таймер в одноимпульсном режиме и потом хотите повторно его запустить, тогда прежде чем это сделать нужно его «остановить» вызвав функцию HAL_TIM_PWM_Stop(&htim4, TIM_CHANNEL_2).

Это связанно с тем, что в функции добавили условия HAL_TIM_STATE_READY и HAL_TIM_STATE_BUSY…





Из-за этого получается следующее: вы запустили таймер в одноимпульсном режиме, в структуру htim->State записался статус HAL_TIM_STATE_BUSY, таймер отработал и остановился, но, статус HAL_TIM_STATE_BUSY так и остался. Поэтому при повторной попытке запуска функция вернёт «занято». Чтобы всё работало, нужно перед запуском вызвать функцию остановки, которая изменит статус на HAL_TIM_STATE_READY.



Если запустить таймер в режиме Input Capture — захват (см. во второй части) и включить One Pulse, то таймер будет совершать захваты, но при этом счёт вестись не будет. То есть значение сравнения и переполнения отработают один цикл, а потом будут всегда равны нулю.




N-pulse waveform generation

Иногда случается ситуация когда нам нужно выдать с таймера несколько импульсов (PWM или Output Compare) и остановится (остановить таймер). Конечно же можно запустить/остановить таймер несколько раз вручную, но есть более изящное, аппаратное, решение, в котором нам поможет Repetition Counter (описан в самом начале) и режим One Pulse.

Для примера настроим таймер №1 как Output Compare в режиме One Pulse


На ножку РА8 подключите светик.


Предделитель (7199) и переполнение (5000) делаем так чтоб светик не очень быстро мигал, Repetition Counter указываем 3, режим Output Compare устанавливаем Toggle on match, и длину импульса прописываем 2000.

Запускаем таймер перед бесконечным циклом…

HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);

В результате светик мигнёт два раза, и таймер остановиться. Если бы мы указали Repetition Counter равным нулю, то светик бы просто загорелся, если 1, то мигнул один раз. Максимальное значение, которое можно записать в Repetition Counter — 255, то есть можно мигнуть 255 раз.

То же самое можно сделать в режиме PWM Generation, то бишь ШИМ…

HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);





Замечание про прерывания.

У Advanced-control timers есть отдельные прерывания для разных событий…



А у более простых таймеров (General-purpose и Basic) одно (глобальное) прерывание для всех событий…



То есть, когда вам нужно ловить прерывание по переполнению, или при захвате, или ещё по какому-то событию, то просто включаете глобальное прерывание, а колбеки прописываете те же, что и в примерах выше.




Ну вот, мы познакомились с относительно простыми функциями таймера — дальше будет посложнее. Тем не менее статья получилась не маленькая, а ещё писать и писать, поэтому продолжение во второй части.


Это всё, всем спасибо


Вторая часть.

И да, прочтение статьи не отменяет изучения официальной документации. Обязательно читайте Reference manual для вашего МК и…

General-purpose timer cookbook AN4776.

Using STM32 device PWM shut-down features for motor control and digital power conversion — AN4277

Presentation on the STM32 timers




Телеграм-чат istarik

Телеграм-чат STM32


  • 0
  • 277970
Поддержать автора


Telegram-чат istarik

Задать вопрос по статье
Telegram-канал istarik

Известит Вас о новых публикациях






Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.