Stm32. программирование stm32f103. timer

Содержание

DM634

Тайваньский чип с 16-ю 16-битными ШИМ-выходами, можно соединять в цепочки. Младшая 12-битная модель известна по отечественному проекту . В свое время, выбирая между DM63x и хорошо известным TLC5940, остановился на DM по нескольким причинам: 1) TLC на Алиэкспрессе точно поддельный, а этот – нет; 2) у DM автономный ШИМ со своим генератором частоты; 3) его можно было недорого купить в Москве, а не ждать посылки с Али. Ну и, конечно, было интересно самому научиться управлять чипом, а не использовать готовую библиотеку. Чипы сейчас в основном представлены в корпусе SSOP24, их несложно припаять на переходник.

Поскольку производитель тайваньский, к чипу написан на китайском английском, а значит, будет весело. Сперва смотрим на распиновку (Pin Connection), чтобы понять, к какой ноге что подключать, и описание пинов (Pin Description). 16 выводов:

Sink / Open-drain output – сток; источник втекающего тока; выход, в активном состоянии подключенный к земле, – светодиоды к драйверу подключаются катодами. Электрически это, конечно, никакой не «открытый сток» (open drain), но в даташитах такое обозначение для выводов в режиме стока встречается часто.

Между пином REXT и землей устанавливается референсный резистор, контролирующий внутреннее сопротивление выходов, см. график на стр. 9 даташита. В DM634 этим сопротивлением можно также управлять программно, устанавливая общую яркость (global brightness); в этой статье вдаваться в подробности не буду, просто поставлю сюда резистор на 2.2 – 3 кОм.

Чтобы понять, как управлять чипом, посмотрим на описание интерфейса устройства:

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

Latch – задвижка/защелка/фиксатор.Rising edge – передний фронт импульсаMSB first – старшим (крайним левым) битом вперед.to clock data – передавать данные последовательно (побитно).

Слово latch часто встречается в документации к чипам и переводится разнообразно, поэтому для понимания позволю себе

LED-драйвер – по сути сдвиговый регистр. «Сдвиг» (shift) в названии – побитное перемещение данных внутри устройства: каждый новый засунутый внутрь бит пихает всю цепочку перед собой вперед. Поскольку во время сдвига никто не хочет наблюдать хаотичное мигание светодиодов, процесс происходит в буферных регистрах, отделенных от рабочих заслонкой (latch) – это своего рода предбанник, где биты выстраиваются в нужную последовательность. Когда все готово, заслонка открывается, и биты отправляются работать, заменяя предыдущую партию. Слово latch в документации к микросхемам почти всегда подразумевает такую заслонку, в каких бы сочетаниях оно ни использовалось.

Итак, передача данных в DM634 осуществляется так: выставляем вход DAI в значение старшего бита дальнего светодиода, дергаем DCK вверх-вниз; выставляем вход DAI в значение следующего бита, дергаем DCK; и так далее, пока все биты не будут переданы (clocked in), после чего дергаем LAT. Это можно сделать вручную (bit-bang), но лучше воспользоваться специально под это заточенным интерфейсом SPI, благо он представлен на нашем STM32 в двух экземплярах.

ШИМ

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

Во-первых, нам надо включить прерывание на 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) по умолчанию на свое:

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

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

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

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

Весь код – .

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

1.1 Базовые характеристики серии STM32F4

Основанные на ARM Cortex-M4 микроконтроллеры серии STM32 F4 являются продолжением ведущей линейки STM32, обладая еще более высокой производительностью. Так же, как серия STM32 F2, эти микроконтроллеры изготавливаются по 90нм-технологии и используют запатентованный ST Microelectronics’ом ART Accelerator для достижения наилучших результатов тестов среди основанных на ядре Cortex-M микроконтроллеров, достигая показателей в 225 DMIPS/606 CoreMark и работая с флеш-памятью на частоте 180 МГц. Инструкции DSP и модуль операций с плавающей точкой дают возможность применять данные контроллеры в широком спектре проектов. Динамическое потребление питания позволяет снизить потребление тока при выполнении кода из флеш-памяти до 140 мкА/МГц для STM32F401 (максимальная частота до 84 МГц) и до 238 мкА/МГц для STM32F42x/43x, работающих на частоте до 180 МГц. Микроконтроллеры серии STM32 F4 являются результатом идеального симбиоза возможности управления МК в реальном времени и производительностью обработки сигналов, свойственной сигнальным процессорам, дополняя таким образом линейку контроллеров STM32 новым классом устройств, сигнальными микроконтроллерами (англ. digital signal controller, DSC).     Серия состоит из пяти классов продуктов, которые полностью совместимы по выводам, периферии и програмному коду.

Характеристики серии:

  • STM32F401– 84 MHz CPU/105 DMIPS. Является экскурсом в серию STM32 F4, предлагая низкое потребление питания и корпуса малых размеров, в отличии от остальных классов серии STM32 F4.
  • STM32F405/415 – 168 MHz CPU/210 DMIPS, до 1 МБайт флеш памяти с расширенными возможностями сопряжения и шифрования.
  • STM32F407/417 – 168 MHz CPU/210 DMIPS до 1 МБайт флеш памяти с наличием Ethernet MAC и интерфейсом камеры для STM32F405/415.
  • STM32F427/437 – 168 MHz CPU/210 DMIPS, до 2 МБайт флеш, дополняя STM32F407/F417 расширенными возможностями сопряжения и шифрования.
  • STM32F429/439 – 180 MHz CPU/225 DMIPS, до 2 МБайт двухбанковой флеш-памяти с интерфейсом SDRAM, контроллером TFT LCD, технологией акселлерации Chrom-ART, последовательным аудиоинтерфейсом, предлагая большую производительность и меньшее энергопотребление по сравнению с STM32F4x7/F4x5.

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.

Настройка порта 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)
{
}

GPIOB

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

PB12CRH:

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);
}

ODRPB12

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;
  PortInit();
  
  for(;;)
  {
    PortSetHi();
    for(i=0; i<0x40000; i++)
      ;

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

BP12«Hello, World!»

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

/// Настраиваем PB15 на вход с подтяжкой к питанию ///
GPIOB->CRH &= ~(GPIO_CRH_MODE15 | GPIO_CRH_CNF15);
//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 на вход с подтяжкой к питанию ///
  GPIOB->CRH &= ~(GPIO_CRH_MODE15 | GPIO_CRH_CNF15);
  //MODE: вход, оставляем в нуле
  //CNF: вход с pull-up / pull-down
  GPIOB->CRH |= (0x00 << GPIO_CRH_MODE15_Pos) | (0x02 << GPIO_CRH_CNF15_Pos);
  
  GPIOB->ODR |= (1<<15); //Включаем подтяжку вверх
}

PB15IDR

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

IDR

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

void main()
{
  PortInit();
  
  for(;;)
  {
    if(ReadPort())
      PortSetHi();
    else
      PortSetLow();
  }
}

stm32f103c8PB12PB15PP12PB15PB15ReadPort()PB12

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

Homework

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.

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 Гц.

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

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

Categories:

Микроконтроллеры

STM32

Please enable JavaScript to view the comments powered by Disqus.

comments powered by Disqus

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

Код

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

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

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

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

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

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

How to Generate Desired/Specific PWM Frequency? Formula derivation and Calculations.

Formula for calculating values for pwm generating is not very complex, it only requires in depth knowledge of the stm32f103 clock, timer structure and timer registers. I divided the formula and method in steps and it is very easy know to calculate the values. Below on right side formula for calculating the value’s for generating specific pwm signal is given and on left side the generic terms used in the formula with their definition/meaning are given.

Timer tick frequency is the frequency at which the timer is completing its one instruction cycle. 

Counter Frequency is the frequency at which we want our timer tick counter to increment.

Stm32f103 Pwm(Pulse Width Modulation) frequency calculation formula

Pwm Resolution

Pwm resolution is number of Pwm steps required to generate a required counter frequency. Usually we use an arbitrary number here and then adjust it according to our need. Pwm resolution must be in between 0-65535 since this value is stored in 16-bit register and maximum 16-bit count is 65535. 

Stm32f103 pwm formula for Prescaller value

Replacing the Counter Frequency with its formula in the Timer Prescaller Value equations yields the equation given on the left side. Now its easy to determine the prescaller value.

Note: Pwm Resolution and Timer Prescaller are 16-bit registers their value must not exceed 65535.  
Now looking at the final formula we need two things in it. Timer Prescaller Value and Pwm Resolutin(Steps).  You can assign Timer Prescaller Value or Pwm Resolution(Steps) a fixed value and calculate the other perimeter. 

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

Чтобы не описывать каждый пункт по отдельности, на рисунках укажу, какие параметры периферии должны быть установлены. Также скажу, что вся работа над проектом велась в 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 соединим друг с другом, а также с каналом осциллографа.

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

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.

Включаем тактирование

За тактирование отвечают часики, они же Clock. И мы уже могли заметить аббревиатуру RCC. Ищем ее в документации: это Reset and Clock Control (Управление сбросом и тактированием).

Как выше было сказано, к счастью, самое сложное из темы тактирования за нас сделали люди из STM, за что им большое спасибо (еще раз дам ссылку на , чтобы было понятно, насколько это заморочено). Нам нужны всего лишь регистры, отвечающие за включение тактирования периферии (Peripheral Clock Enable Registers). Для начала найдем базовый адрес RCC, он в самом начале «Карты памяти»:

И в них, соответственно, биты, включающие тактирование SPI2, IOPB (I/O Port B) и альтернативных функций (AFIO).

Финальный код можно найти .

Если есть возможность и желание потестить, то подключаем DM634 так: DAI к PB15, DCK к PB13, LAT к PB14. Питаем драйвер от 5 вольт, не забываем объединить земли.

ШИМ на STM8

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

К чипу также есть и , в первом распиновка и адреса регистров, во втором – все остальное. Программируется STM8 на C в страшненькой IDE .

Тактирование и ввод-вывод

По умолчанию STM8 работает на частоте 2 МГц, это надо сразу исправить.

Находим адрес регистра в даташите, описание в refman и видим, что регистр надо очистить:

Поскольку мы собираемся запускать ШИМ и подключать светодиоды, смотрим распиновку:

Чип маленький, многие функции подвешены на одни и те же пины. То, что в квадратных скобках – «альтернативный функционал», он переключается «байтами опций» (option bytes) – что-то вроде фьюзов Атмеги. Менять их значения можно программно, но не нужно, т.к. активируется новый функционал только после перезагрузки. Проще воспользоваться ST Visual Programmer (качается вместе с Visual Develop), умеющим менять эти байты. В распиновке видно, что выводы CH1 и CH2 первого таймера спрятаны в квадратные скобки; надо в STVP проставить биты AFR1 и AFR0, причем второй также перенесет вывод CH1 второго таймера с PD4 на PC5.

Таким образом, управлять светодиодами будут 6 пинов: PC6, PC7 и PC3 для первого таймера, PC5, PD3 и PA3 для второго.

Настройка самих пинов ввода-вывода на STM8 проще и логичнее, чем на STM32:

  • знакомый по Atmega регистр направления данных DDR (Data Direction Register): 1 = вывод;
  • первый контрольный регистр CR1 при выводе задает режим «тяни-толкай» (1) или открытый сток (0); поскольку я подключаю светодиоды к чипу катодами, оставляю тут нули;
  • второй контрольный регистр CR2 при выводе задает скорость тактирования: 1 = 10 МГц

Calculating Values for 1Hz frequency and 50% duty cycle pwm signal output

In the final timer settings it’s time to input the Counter Period/Pwm Resolution(Steps), Prescaler value and Pulse required. Pulse is the duty cycle required and in our case its 50%. Lets Solve the upper formula according to the pwm calculation formula’s given above.Given values:Timer Input Clock                                              =                        0.0625Mhz or 62500Hz
Required Frequency                                          =                        1 Hz   (In Time Domain 1 second)
Counter Period/Pwm Resolution(Steps)            =                         3906 (I picked a random value)

Counter Frequency= frequency required * Counter Period
Counter Frequency= 1Hz * 3906 =3906 Hz

Timer Prescaller value=( Timer Input Clock/Counter Frequency)-1
Timer Prescaller value=(62500 Hz / 3906 Hz) – 1 = 15

Now we have both the value’s Timer Presaller value, Counter Period/Pwm Resolution(Steps) and also they are in 16-bit range. Now input the values in the timer 4 configuration. I selected the counter to be in UP mode(Count for 0 to 3906). Pulse is the duty cycle in our case its 50% so 3906*50% > 3906*0.5 = 1953.

Stm32f103 timer 4 pwm settings

Next advance tutorial on Variable Pwm(Pulse Width Modulation) Output with Stm32f103 microcontroller using stm32cubemx and keil uvision ide. Click the below button to move to tutorial.

Download the Project code. Folder contains the stmcubemx project file and keil Mdk-Arm v5 project files. Please give us your feed back on the project.

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

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

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

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

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

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

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

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