Настроим проект

PWM Output

In STM32 we have much more options and control for PWM generation compared to Arduino, although we’ll stick to the basics for this lesson by using it to dim the on-board LED on PA4.

Timer configuration

We’ll continue working on the project from the previous section. Go back to STM32Cube and switch to page:

Just like Arduino, only certain pins on STM32 has PWM output. You can find out by left clicking a pin and see if it as function, where is timer number, and is channel number. Let’s see what’s on the on-board LED, :

Bingo! has , meaning it’s the output of timer 14 channel 1, and we can use it for PWM.

Click to switch to that function:

Then on the side bar, activate TIM14, then set channel 1 function to :

Go to the page and click on the newly appeared button:

Now we’re back at the counter settings, except this time there is an additional PWM1 section:

Just like last time, we have and . Except in this case, instead of the interrupt frequency, they determine the PWM frequency.

Let’s say we want the PWM frequency of our LED to be 1KHz. One option is to use of 47 and of 1000.

The of 47 divides the 48MHz system clock down to 1MHz for the TIM14 counter, making it count up every 1us. And the of 1000 makes the counter reset once the count has reached 1000. This way the counter resets every 1000uS, which is 1ms, resulting in a PWM frequency of 1KHz.

The new parameter determines the duty cycle of the PWM output, calculated as .

For example, if is 1000 and you set to 900, then the duty cycle is , meaning the output stays high 90% of the time during a single PWM period. Obviously, should be less or equal to .

Anyway, let’s type the numbers in and see what happens:

Generate the code, then go to Keil IDE.

Try it out

To see PWM in action we need to make a few changes in the code. Before the main loop, call a few more initialization functions:

Then delete everything in the main loop. It should look like this:

Compile and upload. Now LED should be much dimmer than before, almost unnoticeable compared to the power LED:

And if you have a logic analyzer, take a look at the waveform:

You’ll see that the PWM signal has a period of 1ms, and stays high 90% of the time, exactly what we want.

Eagle eyed viewer might spot the LED is dim even though the duty cycle is high. Remember that the LED on PA4 is connected to 3.3V on the other end, so it lights up when PA4 is LOW.

Changing duty cycle on the fly

Setting up duty cycle in STM32Cube is all well and good, but it would be more useful if we can change it while our program is running. Luckily you can manipulate peripheral registers directly in STM32 HAL. For adjusting duty cycle, simply write into register like this:

CCR1 is for channel 1, change it to CCR2, CCR3, CCR4 if you you’re using other channels.

Low-level peripheral registers is another massive rabbit hole that I’m not going into. After all, abstracting them is the job for HAL library in the first place.

However if you’re feeling adventurous, feel free to dive in the thousand-page reference manual and find out the details about every single peripheral registers. Then you can find their s in the beginning of device header file and manipulate them directly.

Anyway, we can try it out by adding this in the main loop:

It ramps up the duty cycle, then back down again, resulting in a smooth «breathing» animation:

You can find the finished code here

What we’re doing here is fairly similar to just calling in Arduino. However, even in this simple example we have much more control of our PWM properties. We can set the PWM frequency to whatever we like, while Arduino is fixed to a measly 490Hz or 980Hz. We also have control of the PWM resolution by changing from 0 to 65535, while Arduino is fixed to 255.

Of course what we’re covering here is the very basics of STM32 timers, and they can do much more than that. If you’re interested in experimenting with advanced timer functions feel free to go back to timer overview and the datasheet

STM32 Timers

Each STM32 variant has several built-in timers. They are numbered from TIM1 to TIM20, and grouped into different types:

  • : As the name suggests, those are the standard timers with functions like output compare, one-pulse, input capture, sensor interface, interrupts, PWM output, and many more.

  • : Those come with even more bells and whistles, usually highly specialized like motor control and digital power conversion.

  • : Barebone timers with no outputs. Usually used for timekeeping.

  • : Chip-specific timers like low-power or high-resolution timers.

Usually the higher-end the chip, the more timer it has. The details of STM32 timers is quite a rabbit hole, but we’ll keep it simple in this lesson.

If you want to learn more, Here is an cross-series overview of STM32 timers. There are also helpful information in the timer section of the datasheet of chip we’re using.

Watchdog Timer

Watchdog timers(WDT) are often used to reset the system when a malfunction occurs. In normal operation the WDT counts up(or down) while your program periodically resets it. This is called «kicking the dog». If your program crashed and did not kick WDT in time, it will overflow and generate a hardware reset signal. This restarts the system and recovers it from the crash.

STM32 watchdog timers

STM32 has two watchdog timers: Independent Watchdog (IWDG) and System Window Watchdog (WWDG).

IWDG is a 12-bit down-counter clocked from an independent internal clock source. It counts down from 4095 and resets the system if it reaches 0.

WWDG has more bells and whistles, featuring fancy stuff like early warning interrupt and so on.

Usually IWDG is more than enough, so that’s what we’re looking at.

IWDG setup

Enable the IWDG:

Click its button:

And now we’re at its configuration window:

Usually there is no need to change and . Of course you can read more here.

The bit we’re interested in is, again, the prescaler. This determine how fast the IWDG counts down towards reset. Assuming you didn’t change the other two values, you can consult the table below to see how long is the reset window:

After regenerating the code, simply use to kick the dog before the countdown reaches 0.

Keep in mind that the internal 40KHz clock powering the IWDG is not very accurate, and can vary as much as ±10%. So leave some slack when setting IWDG parameters.

Here is a simple finished project with IWDG. It prints out an incrementing number while also kicking the dog. If you comment out the function, you’ll see that the counting restarts every 3 seconds or so, indicating the IWDG is resetting the chip.


Now that we have covered the basics about STM32 timers, it’s up to you to implement something useful with them. This homework is a bit longer than usual, but it does challenge you to put everything you learned into use.

The goal is to implement an accurate microsecond timebase with STM32 timers.

In the end you should have a function which returns the number of microseconds since start-up, and a function which delays the number of microseconds in the argument.

I suggest implementing the functions in separate files like we did last time, so you can reuse them afterwards.

You can access the current counter value in a timer with , where is the timer number.

For further hints and the answer, click here.

Настройка порта GPIO

Итак, с регистрами разобрались, настало время практики. Все примеры в этой статье для микроконтроллера STM32F103C8. В моем распоряжении есть вот такая отладочная плата:

На ней установлен кварцевый резонатор на 8 МГц и светодиод на порту PB12. Вот с помощью этого светодиода мы и устроим Hello, World!

Задача ясна: настраиваем PB12 на выход в режиме push-pull и с помощью регистра ODR дергаем 12-й пин порта GPIOB туда-сюда! Но мы забыли об одной маленько детали: RCC. Дело в том, что по-умолчанию после сброса микроконтроллера все периферийные модули отключены от источника тактового сигнала, в том числе и GPIO. А подать тактирование можно с помощью регистров RCC. В 3-ей части я про это говорил. Для начала нужно определить, к какой шине у нас подключен GPIOB. Открываем даташит на микроконтроллер, ищем вот эту таблицу:

Рис. 8. Таблица шин и периферийных устройств

GPIOB у нас подключен к шине APB2. Идем в Reference manual, открываем раздел про RCC, переходим к пункту 7.3.7 APB2 peripheral clock enable register (RCC_APB2ENR). С помощью этого регистра можно подать тактовый сигнал на устройства шины APB2:

Рис. 9. Регистр RCC_APB2ENR

В регистре RCC_APB2ENR много флагов для разной периферии, в том числе и для нашего GPIOB, флаг называется IOPBEN. Перед началом инициализации PB12 нам надо установить этот бит в единицу.

Поехали программировать! За основу возьмем проект из 2-й части: https://github.com/DiMoonElec/stm32f103c8_empty_project. Создадим функцию инициализации порта:

void PortInit(void)


RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Включаем тактирование порта GPIOB


GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //для начала все сбрасываем в ноль
//MODE: выход с максимальной частотой 2 МГц
//CNF: режим push-pull
GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos);
void PortInit(void)
  RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Включаем тактирование порта GPIOB
  GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //для начала все сбрасываем в ноль
  //MODE: выход с максимальной частотой 2 МГц
  //CNF: режим push-pull
  GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos);


void PortSetHi(void)
  GPIOB->ODR |= (1<<12);
void PortSetLow(void)
  GPIOB->ODR &= ~(1<<12);

GPIOB->ODR |= (1<<12)STM32

void PortSetHi(void)
  GPIOB->BSRR = (1<<12);

void PortSetLow(void)
  GPIOB->BRR = (1<<12);

 (1<<12)0x1000BSRRBRRрис. 5, 6

Ни и простой main() для проверки:

void main()
  int i;
    for(i=0; i<0x40000; i++)

    for(i=0; i<0x40000; i++)

BP12«Hello, World!»

Давайте теперь настроим какой-нибудь вывод порта, например PB15, на вход с подтяжкой к питанию. При подключении PB15 к минусу, у нас будет зажигаться светодиод. Задача ясна, преступаем к реализации. В PortInit() добавим пару строк:

/// Настраиваем PB15 на вход с подтяжкой к питанию ///
//MODE: вход, оставляем в нуле
//CNF: вход с pull-up / pull-down
GPIOB->CRH |= (0x00 << GPIO_CRH_MODE15_Pos) | (0x02 << GPIO_CRH_CNF15_Pos);

GPIOB->ODR |= (1<<15); //Включаем подтяжку вверх

PB12MODE/CNFODRODRCNF=10Input with pull-up / pull-downODRReference manual

Рис. 10. Таблица конфигурации порта

Функция PortInit() приобретает такой вид:

void PortInit(void)
  RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Включаем тактирование порта GPIOB
  /// Настраиваем PB12 на выход ///
  GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //для начала все сбрасываем в ноль
  //MODE: выход с максимальной частотой 2 МГц
  //CNF: режим push-pull
  GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos);
  /// Настраиваем PB15 на вход с подтяжкой к питанию ///
  //MODE: вход, оставляем в нуле
  //CNF: вход с pull-up / pull-down
  GPIOB->CRH |= (0x00 << GPIO_CRH_MODE15_Pos) | (0x02 << GPIO_CRH_CNF15_Pos);
  GPIOB->ODR |= (1<<15); //Включаем подтяжку вверх


int ReadPort(void)
  if(GPIOB->IDR & (1<<15))
    return 1;
  return 0;


В этом случае main() будет выглядеть вот так:

void main()


На этом все, продолжение следует! Продолжение.

Что еще?

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

Clock security system (CSS) — переводится примерно как «система безопасности тактирования». Если, при использовании генератора HSE в качестве источника тактового сигнала для SYSCLK или PLL, произойдет срыв генерации HSE, то CSS автоматически переключит всю систему на работу от встроенного RC-генератора HSI. Таким образом, если что-то случится с кварцем, система не зависнет намертво в неопределенном состоянии, а сможет выполнить какие-то действия, например, перевести объект управления в безопасное состояние (закрыть все вентили, отключить силовые установки, и т.д.)

Модуль часов реального времени RTC может тактироваться от встроенного LSI генератора на 40 КГц, от HSE через делитель на 128, либо от LSE с внешним кварцем на 32768 Гц. Источник тактовых импульсов выбирается с помощью RTCSEL.

Модуль USB получает тактовый сигнал от PLL, причем при частоте на выходе PLL равной 72 МГц есть возможность активировать USB Prescaler с коэффициентом деления 1.5 для получения необходимой частоты 48 МГц.

Microcontroller clock output (MCO) — вывод микроконтроллера, на который можно вывести частоту от одного из источников сигнала: SYSCLK, HSE, HSI либо сигнал с выхода PLL, поделенный пополам. Нужный источник выбирается с помощью битов MCO.

Проверка на железе

Подключив два щупа осциллографа к MCU (один к пину DAC/ADC IN1, другой к выходу TIM3_CH1) Можем наблюдать следующее изображение:

Желтый — сигнал с DAC, зеленый — выход TIM3_CH1

На осциллограмме каждое переключение зеленого сигнала является моментом измерения. Можно увидеть, что присутствует разброс по времени между измерениями.

Теперь перенесем измеренные значения ADC на график и посмотрим, что получилось:

Результат измерения, по горизонтальной оси значения CCR, по вертикальной оцифрованные значения DAC

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

Я попытался повторить тот же самый трюк на STM32F103C8T6, но к сожалению мне не удалось добиться такого же эффекта. Как только происходит первое событие по сравнению с CCR, MCU тот час же пытается оцифровать сразу все запрошенное количество, не дожидаясь следующих событий. Если у кого-то получится это реализовать, я с радостью добавлю в статью.


ШИМ на втором таймере настраивается так же, как в предыдущей истории, с двумя отличиями:

Во-первых, нам надо включить прерывание на Update Event (UEV), которое будет вызывать функцию, переключающую активный LED. Делается это изменением бита Update Interrupt Enable в регистре с говорящим названием

Второе отличие связано с таким явлением мультиплексинга, как ghosting – паразитное свечение диодов. В нашем случае оно может появитсья из-за того, что таймер, вызвав прерывание на UEV, идет тикать дальше, и обработчик прерывания не успевает переключить LED прежде чем таймер уже начнет что-то писать в выводы

Для борьбы с этим придется инвертировать логику (0 = максимальная яркость, 255 = ничего не горит) и не допускать крайних значений скважности. Т.е

добиться того, чтобы после UEV светодиоды полностью гасли на один такт ШИМ.

Меняем полярность:

Избегаем установки r, g и b в 255 и не забываем их инвертировать при использовании.


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

Когда мы в первый раз создали проект в ST Visual Develop, то кроме мы получили окно с загадочным файлом , автоматически включенным в проект. В этом файле на каждое прерывание привязана функция . Нам надо привязать свою функцию к нужному прерыванию.

В даташите есть таблица векторов прерываний, где мы находим нужные:

Нам надо менять LED при UEV, так что нужно прерывание №13.

Соответственно, во-первых, в файле меняем имя функции, отвечающей за прерывание №13 (IRQ13) по умолчанию на свое:

Во-вторых, нам придется создать файл такого содержания:

Ну и, наконец, прописать эту функцию в своем :

Осталось включить прерывания. Делается это ассемблерной командой – искать ее придется в :

Другая ассемблерная команда – – выключает прерывания. Их надо отключать на время записи новых значений в «видеопамять», чтобы вызванное в неудачный момент прерывание не испортило массив.

Весь код – .

Если хоть кому-то эта статья пригодится, значит, я не зря ее писал. Буду рад комментариям и замечаниям, постараюсь ответить на все.

Источники питания

STM32W108 содержит три системы источников питания. Всегда включенный высоковольтный источник питания обеспечивает работу GPIO и функционирование критических блоков микросхемы. Остальные блоки микросхемы питаются от низковольтных стабилизаторов. Низковольтные источники питания можно отключить при переходе в спящий режим, что дополнительно уменьшает энергопотребление. Внутренние стабилизаторы обеспечивают получение напряжений питания 1,25 В и 1,8 В из нерегулируемого напряжения питания микросхемы. Выход стабилизатора напряжения 1,8 В имеет внешний фильтр и может использоваться внешними аналоговыми блоками, RAM и flash-памятью. Выход стабилизатора напряжения 1,25 В имеет внешний фильтр и используется для питания ядра микропроцессора.

Режимы пониженного энергопотребления

STM32W108 имеет сверхнизкое энергопотребление в режиме глубокого сна с возможностью выбора способа тактирования. Таймер выхода из состояния бездействия можно тактировать или от внешнего кварцевого резонатора на частоту 32,768 кГц, или от сигнала частотой 1 кГц, полученного делением частоты 10 кГц от внутреннего RC-генератора. Для режима с наименьшим энергопотреблением все тактовые генераторы можно выключить, т.к. микросхема будет пробуждаться только внешними событиями с выводов GPIO. STM32W108 обладает быстрым временем пробуждения (типичное значение — 100 мкс) из состояния глубокого сна до момента выполнения первой инструкции ARM Cortex-M3.

Средства разработки и отладки

Для создания приложений на базе STM32W108 можно использовать широкий набор сред разработки и отладки, а также операционных систем реального времени (OS и RTOS), предназначенных для работы с ARM-микроконтроллерами, и предлагаемых многими ведущими производителями программного и аппаратного обеспечения. Для создания прикладных программ для STM32W108 можно использовать интегрированную среду разработки Keil RealView Microcontroller Development Kit (MDK) совместно с семейством USB-JTAG адаптеров Keil ULINK или IAR Embedded Workbench for ARM совместно с адаптером IAR J-Trace for Cortex-M3.

Отличительной особенностью STM32W108 является наличие аппаратной поддержки модуля трассировки пакетов, который обеспечивает многоуровневую отладку на уровне пакетов. Этот блок является необходимым компонентом для интегрированной среды разработки InSight Desktop компании Ember и при использовании специального адаптера InSight компании Ember обеспечивает возможность расширенной сетевой отладки.

STM32 Часть 8–Таймеры общего назначения (+прерывания)

15 Января 2011

Хотел заняться АЦП, но похоже что перед этим нужно все таки попробовать в работе любой таймер, проще всего показалось использовать таймер общего назначения (General-purpose).

Основная задача которую будет выполнять счетчик это генерация прерываний через установленный промежуток времени.

Таймеры общего назначения

В состав микроконтроллера STM32F100RBT6B входят шесть таймеров общего назначения TIM2, TIM3, TIM4, TIM15, TIM16, TIM17 (и один таймер с расширенными настройками TIM1), честно говоря и таймеры общего назначения имеют немало настроек и возможностей.

Таймеры выделены в три группы по набору возможностей:

  • TIM2, TIM3, TIM4
  • TIM15
  • TIM16, TIM17

Все таймеры имеют разрядность 16-бит, однако есть возможность сконфигурировать последовательное соединение двух таймеров (один в режиме “ведущий” другой в режиме “ведомый”).

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

На первый взгляд самые простые за номерами 16 и 17, думаю с них и начнем.

Источником тактовых сигналов для таймера могут быть как внешние сигналы, так и сигналы тактового генератора МК:

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

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

На данном этапе воспользуемся внутренним источником, для этого необходимо разрешить в модуле RCC прохождение тактового сигнала до соответствующего таймера, буду рассматривать таймер 17:

далее необходимо установить предварительный делитель на необходимый коэффициент деления.

Я хочу использовать таймер для отсчета временных интервалов в 1 мс, а так как я по прежнему использую внутренний RC генератор с частотой 8 МГц, установим предварительный делитель равным 1000, таким образом значения счетчика будут инкрементироваться с частотой 8000 Гц:

Примечание: коэффициент деления равен единице при нулевом значении предварительного делителя, таким образом для получения коэффициента деления N, необходимо установить значения предварительного делителя в N-1.

а максимальное значение до которого будет считать таймер установим равным восьми:

После того как таймер достигнет своего максимального значения необходимо что бы он уведомил об этом контроллер прерываний, для этого разрешим генерацию событий:

Осталось только разрешить работу таймера и сброс по достижении максимального значения:

Хочется надеяться, что и дальше будет всё так просто 😉

Контроллер прерываний

Описание контроллера прерываний вынесено в отдельный документ, так как он является стандартным блоком ядра Cortex-M3:

И не понятно по чему определения вынесены из заголовочного файла stm32f10x.h, пришлось брать заголовочный файл core_cm3.h от CMSIS.

Описание на русском можно почитать на gaw.ru

Я же не буду углубляться в его структуру и логику работы.

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

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

У данного микроконтроллера к событиям таймера 17, добавлены еще два: от таймера 1 (TIM1 Trigger) и от Commutation (пока не смотрел что это за событие) и привязаны к 26 вектору.

Внесем изменения в нашу таблицу прерываний, т.е. пропишем адрес обработчика:

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

Устанавливаем приоритет и разрешаем прерывания:

Вроде ни чего не забыл, можно приступать к проверке.

Для проверки пропишем в обработчик небольшую логику:

При входе в прерывания необходимо проверить источник и сбросить флаг (по крайней мере для таймера 17 указанно что данный флаг сбрасывается программно). Далее для теста я завел глобальную переменную test_count при каждом вхождении инкрементирую её и проверяю на ряд значений (0, 1000, 2000), при нулевом значении включаю зеленый светодиод, при значении 1000 выключаю и при достижении 2000 обнуляю. Задумка — просто мигать зеленым светодиодом с частотой 0,5 Гц.

Всю необходимую информацию вы можете найти на офф стайте:

Проект с примером:




Please enable JavaScript to view the comments powered by Disqus.

comments powered by Disqus


Запишем нулевое значение в DAC и включим его. Далее в тестовом примере будет написана функция, которая постепенно увеличивает выходное значение DAC до достижения им потолка (4095).

Создадим массив для CCR значений и запишем в него случайные возрастающие значения, которые меньше чем значение, хранящееся в ARR, чтобы провести все измерения за один цикл таймера. Случайные значения взяты для того, чтобы глазами можно было убедиться в том, что сигнал действительно измеряется в разные не периодические моменты времени.

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

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

И следом запускается таймер с DMA2, при этом количество транзакций должно быть на единицу меньше, т.к. первое число уже находится в регистре CCR1.

Осталось запустить на железке и проверить.

Разберёмся с прерываниями

Окей, прерывания. Для меня когда–то (во времена PIC) они были тёмным лесом, и я старался вообще их не использовать — да и не умел, на самом деле. Однако, в них заключена сила, игнорировать которую вообще недостойно. Правда, прерывания в STM32 — ещё более сложная штука, особенно механизм их вытеснения; но об этом позже.

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

  1. Включить вообще прерывания таймера TIM6;
  2. Включить прерывание таймера TIM6 на переполнение счётчика;
  3. Написать процедуру–обработчик прерывания;
  4. После обработки прерывания сбросить его.


Включение прерываний

Честно говоря, тут вообще ничего сложного. Первым делом включаем прерывания TIM6: NVIC_EnableIRQ(TIM6_DAC_IRQn); Почему такое название? Потому что в ядре STM32 прерывания от TIM6 и от ЦАП имеют одинаковый номер. Не знаю, почему так сделано — экономия, нехватка номеров или просто какая–то наследная штука — в любом случае, никаких проблем это не принесёт, потому что в этом проекте не используется ЦАП. Даже если в нашем проекте использовался бы ЦАП — мы могли бы при входе в прерывание узнавать, кто конкретно его вызвал. Практически все другие таймеры имеют единоличное прерывание.

Настройка события–источника прерываний: TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE); — включаем прерывание таймера TIM6 по событию TIM_DIER_UIE, т.е. событие обновления значения ARR. Как мы помним из картинки, это происходит одновременно с переполнением счётчика — так что это именно то событие, которое нам нужно.

На текущий момент код таймерных дел таков:

Обработка прерываний

Сейчас запускать проект нельзя — первое же прерывание от таймера не найдёт свой обработчик, и контроллер повиснет (точнее, попадёт в обработчик HARD_FAULT, что по сути одно и то же). Нужно его написать.

Немного теории

Он должен иметь совершенно определённое имя, void TIM6_DAC_IRQHandler(void). Это имя, так называемый вектор прерывания, описано в файле startup (в нашем проекте это startup_stm32f10x_md_vl.s — можете сами увидеть, 126 строка). На самом деле вектор — это адрес обработчика прерывания, и при возникновении прерывания ядро ARM лезет в начальную область (в которую транслирован файл startup — т.е. его местоположение задано совершенно жёстко, в самом начале флеш–памяти), ищет там вектор и переходит в нужное место кода.

Проверка события

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

В нашей программе эта проверка будет выглядеть так: if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) — всё понятно, функция TIM_GetITStatus проверяет наличие указанного события у таймера, и возвращает 0 или 1.

Очистка флага UIF

Второй шаг — очистка флага прерывания. Вернитесь к картинке: самый последний график UIF это и есть флаг прерывания. Если его не очистить, следующее прерывание не сможет вызваться, и контроллер опять упадёт в HARD_FAULT (да что же такое!).

Выполнение действий в прерывании

Будем просто переключать состояние светодиода, как и в первой программе. Разница в том, что теперь наша программа делает это более сложно! На самом деле, так писать гораздо правильнее.

Используем глобальную переменную int state=0;

Настройка периферии

Чтобы не описывать каждый пункт по отдельности, на рисунках укажу, какие параметры периферии должны быть установлены. Также скажу, что вся работа над проектом велась в STM32CubeIDE.

1. Для ADC должен быть установлен Scan Conversion Mode, выбран канал(IN1), настроен триггер и DMA1, который будет перекладывать готовые измерения в память.

2. Настройка таймера. Установку значений предделителя и AutoReload Register я опущу, т.к. они будут зависеть от вашего проекта. Для данного примера я экспериментально подобрал такие значения, которые бы позволили мне провести наглядный эксперимент, который я опишу ниже. Также для TIM3 необходимо добавить DMA, и указать его направление — из памяти в периферию, а также поставить галочку в пункте Increment Address для Memory. Прерывания для TIM3 устанавливались опционально для того, чтобы проверить, что в CCR каждый раз записываются новые значение с помощью DMA. Также в настройках конфигурации Output Compare CH1 может быть заменен на Output Compare No Output, если нет необходимости отображать состояние сравнения CCR с CNT на пине MCU.

Установка режима Toogle on match в TIM3 Output Compare Channel 1 позволяет отрабатывать каждое событие по CCR в ADC. Только в таком режиме со связкой в ADC : Trigger detection on both the rising and failling edges мне удалось заставить ADC запускать каждое преобразование.

3. На этом настройка периферии закончена, но для экспериментальной проверки включим DAC, который будет генерировать значения для оцифровывания, а пины микроконтроллера DAC и ADC IN1 соединим друг с другом, а также с каналом осциллографа.

С настройкой периферии разобрались, теперь необходимо написать код и провести эксперимент.