admin / 15.12.2017

Программирование STM32. Часть 1. GPIO, порты ввода-вывода STM32

В STM32 есть множество очень удобных и гибких в настройке таймеров. Даже у самого младшего микроконтроллера (STM32F030F4P6) есть 4 таких таймера.

Содержание

8. Настроим проект — добавим нужные файлы

Чтобы использовать таймер, нам потребуется подключить файл библиотеки периферии stm32f10x_tim.c. Точно так же, правой кнопкой щёлкаем в Workspace (окно слева) по группе StdPeriphLib, Add –> Add files, файл LibrariesSTM32F10x_StdPeriph_Driversrcstm32f10x_tim.c.

Ещё нужно включить использование заголовка к этому файлу. Открываем stm32f10x_conf.h (правой кнопкой по названию этого файла в коде, «Open stm32f10x_conf.h». Раскомментируем строчку #include «stm32f10x_tim.h».

9. Добавим таймер

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

В STM32 есть разные таймеры, отличающиеся набором свойств. Самые простые — Basic timers, посложнее — General purpose timers, и самые сложные — Advanced timers. Простые таймеры ограничиваются просто отсчётом тактов. В более сложных таймерах появляется ШИМ. Самые сложные таймеры, к примеру, могут сгенерировать 3–фазный ШИМ с прямыми и инверсными выходами и дедтаймом. Нам хватит и простого таймера, под номером 6.

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

Всё, что нам требуется от таймера — досчитывать до определённого значения и генерировать прерывание (да, мы ещё и научимся использовать прерывания). Таймер TIM6 тактируется от системной шины, но не напрямую а через прескалер — простой программируемый счётчик–делитель (подумать только, в СССР выпускались специальные микросхемы–счётчики, причём программируемые были особым дефицитом — а теперь я говорю о таком счётчике просто между делом). Прескалер можно настраивать на любое значение от 1 (т.е. на счётчик попадёт полная частота шины, 24МГц) до 65536 (т.е. 366 Гц).

Тактовые сигналы в свою очередь, увеличивают внутренний счётчик таймера, начиная с нуля. Как только значение счётчика доходит до значения ARR — счётчик переполняется, и возникает соответствующее событие. По наступлению этого события таймер снова загружает 0 в счётчик, и начинает считать с нуля. Одновременно он может вызвать прерывание (если оно настроено).

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

Код

Код будет очень похож на предыдущий, т.к. инициализация всей периферии происходит однотипно — за тем лишь исключением, что таймер TIM6 висит на шине APB1. Поэтому включение таймера: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);

Теперь заводим структуру типа TIM_TimeBaseInitTypeDef, инициализируем её (TIM_TimeBaseStructInit), настраиваем, передаём её в функцию инициализации таймера (TIM_TimeBaseInit) и наконец включаем таймер (TIM_Cmd).

Что за магические числа? Как мы помним, на шине присутствует тактовая частота 24МГц (при наших настройках проекта). Настроив предделитель таймера на 24000, мы поделим эту частоту на 24 тысячи, и получим 1кГц. Именно такая частота попадёт на вход счётчика таймера.

Значение же в счётчике — 1000. Значит, счётчик переполнится за 1000 тактов, т.е. ровно за 1 секунду.

После этого у нас действительно появляется работающий таймер. Но это ещё не всё.

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

Окей, прерывания. Для меня когда–то (во времена 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;

11. Весь код проекта с таймером

Архив с проектом таймера.

Ну и к слову, таймер умеет переключать ногу и сам, без прерываний и ручной обработки. Это будет наш третий проект.

Весь цикл:

1. Порты ввода–вывода

2. Таймер и прерывания

3. Выходы таймера

4. Внешние прерывания и NVIC

5. Ставим FreeRTOS

Недавно коллега меня подсадил на идею создания умного дома, я даже успел заказать себе десятки разных датчиков. Встал вопрос о выборе Микроконтроллера (далее МК) или платы. После некоторых поисков нашёл несколько вариантов. Среди них были и Arduino (включая его клоны, один из которых себе заказал ради того, чтобы просто побаловаться) и Launchpad, но всё это избыточно и громоздко (хотя в плане программирования гораздо проще, но тему холиваров поднимать не буду, у каждого свои вкусы). В итоге решил определяться не с готовой платой, а взять только МК и делать всё с нуля. В итоге выбирал между Atmel ATtiny (2313), Atmel ATmega (решил отказаться т.к. не смог найти за адекватные деньги), STM32 (Cortex на ядре ARM). С тинькой я уже успел побаловаться, так что взял себе STM32VL-Discovery. Это можно назвать вступлением к циклу статей по STM32. Оговорюсь сразу, автором большинства этих статей буду являться не я, т.к. сам только познаю, здесь я публикую их в первую очередь для себя, чтоб удобнее было искать если что-то забуду. И так поехали!

Общие сведения

Микроконтроллеры семейства STM32 содержат в своём составе до семи 16-разрядных портов ввода-вывода c именами от PORTA до PORTG. В конкретной модели микроконтроллера без исключений доступны все выводы портов, общее количество которых зависит от типа корпуса и оговорено в DataSheet на соответствующее подсемейство.

Для включения в работу порта x необходимо предварительно подключить его к шине APB2 установкой соответствующего бита IOPxEN в регистре разрешения тактирования периферийных блоков RCC_APB2ENR:

Управление портами STM32 осуществляется при помощи наборов из семи 32-разрядных регистров:

  • GPIOx_CRL, GPIOx_CRH – задают режимы работы каждого из битов порта в качестве входа или выхода, определяют конфигурацию входных и выходных каскадов.
  • GPIOx_IDR – входной регистр данных для чтения физического состояния выводов порта x.
  • GPIOx_ODR– выходной регистр осуществляет запись данных непосредственно в порт.
  • GPIOx_BSRR – регистр атомарного сброса и установки битов порта.
  • GPIOx_BSR – регистр сброса битов порта.
  • GPIOx_LCKR – регистр блокировки конфигурации выводов.

Режимы работы выводов GPIO

Режимы работы отдельных выводов определяются комбинацией битов MODEy[1:0] и CNFy[1:0] регистров GPIOx_CRL и GPIOx_CRH (здесь и далее: x-имя порта, y- номер бита порта).

GPIOx_CRL — регистр конфигурации выводов 0…7 порта x:

Структура регистра GPIOx_CRH аналогична структуре GPIOx_CRL и предназначена для управления режимами работы старших выводов порта (биты 8…15).

Биты MODEy указанных регистров определяют направление вывода и ограничение скорости переключения в режиме выхода:

  • MODEy[1:0] = 00: Режим входа (состояние после сброса);
  • MODEy[1:0] = 01: Режим выхода, максимальная скорость – 10МГц;
  • MODEy[1:0] = 10: Режим выхода, максимальная скорость – 2МГц;
  • MODEy[1:0] = 11: Режим выхода, максимальная скорость – 50МГц.

Биты CNF задают конфигурацию выходных каскадов соответствующих выводов:

в режиме входа:

  • CNFy[1:0] = 00: Аналоговый вход;
  • CNFy[1:0] = 01: Вход в третьем состоянии (состояние после сброса);
  • CNFy[1:0] = 10: Вход с притягивающим резистором pull-up (если PxODR=1) или pull-down (если PxODR=0);
  • CNFy[1:0] = 11: Зарезервировано.

в режиме выхода:

  • CNFy[1:0] = 00: Двухтактный выход общего назначения;
  • CNFy[1:0] = 01: Выход с открытым стоком общего назначения;
  • CNFy[1:0] = 10: Двухтактный выход с альтернативной функцией;
  • CNFy[1:0] = 11: Выход с открытым стоком с альтернативной функцией.

С целью повышения помехоустойчивости все входные буферы содержат в своём составе триггеры Шмидта. Часть выводов STM32, снабженных защитными диодами, соединёнными с общей шиной и шиной питания, помечены в datasheet как FT (5V tolerant) — совместимые с напряжением 5 вольт.

Защита битов конфигурации GPIO

Для защиты битов в регистрах конфигурации от несанкционированной записи в STM32 предусмотрен регистр блокировки настроек GPIOx_LCKR
GPIOx_LCKR — регистр блокировки настроек вывода порта:

Для защиты настроек отдельного вывода порта необходимо установить соответствующий бит LCKy. После чего осуществить последовательную запись в разряд LCKK значений "1” — "0” — "1” и две операции чтения регистра LCKR, которые в случае успешной блокировки дадут для бита LCKK значения "0” и "1” .

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

Файл определений для периферии микроконтроллеровSTM32 определяет отдельные группы регистров, объединённые общим функциональным назначением (в том числе и GPIO), как структуры языка Си, а сами регистры как элементы данной структуры.

STM32 — с нуля до RTOS. 2: Таймер и прерывания

Например:

GPIOC->BSRR – регистр BSRR установки/сброса порта GPIOC.
Воспользуемся определениями из файла stm32f10x.h для иллюстрации работы с регистрами ввода-вывода микроконтроллера STM32F100RB установленного в стартовом наборе STM32VLDISCOVERY:

Запись и чтение GPIO

Для записи и чтения портов предназначены входной GPIOx_IDR и выходной GPIOx_ODR регистры данных.

Запись в выходной регистр ODR порта настроенного на вывод осуществляет установку выходных уровней всех разрядов порта в соответствии с записываемым значением. Если вывод настроен как вход с подтягивающими резисторами, состояние соответствующего бита регистра ODR активирует подтяжку вывода к шине питания (pull-up, ODR=1) или общей шине микроконтроллера (pull-down, ODR=0).

Чтение регистра IDR возвращает значение состояния выводов микроконтроллера настроенных как входы:

Сброс и установка битов порта

Для атомарного сброса и установки битов GPIO в микроконтроллерах STM32 предназначен регистр GPIOx_BSRR. Традиционный для архитектуры ARM способ управления битами регистров не требующий применения операции типа "чтение-модификация-запись” позволяет устанавливать и сбрасывать биты порта простой записью единицы в биты установки BS (BitSet) и сброса BR (BitReset) регистра BSRR. При этом запись в регистр нулевых битов не оказывает влияния на состояние соответствующих выводов.

GPIOx_BSRR – регистр сброса и установки битов порта:

Альтернативные функции GPIO и их переназначение (remapping)
Практически все внешние цепи специального назначения STM32 (включая выводы для подключения кварцевых резонаторов, JTAG/SWD и так далее) могут быть разрешены на соответствующих выводах микроконтроллера, либо отключены от них для возможности их использования в качестве выводов общего назначения. Выбор альтернативной функции вывода осуществляется при помощи регистров с префиксом "AFIO”_.
Помимо этого регистры AFIO_ позволяют выбирать несколько вариантов расположения специальных функций на выводах микроконтроллера. Это в частности относится к выводам коммуникационных интерфейсов, таймеров (регистры AFIO_MAPR), выводам внешних прерываний (регистры AFIO_EXTICR) и т. д.

Подробнее смотрите документы "Reference manual” на соответствующую подгруппу микроконтроллеров.

Проекты к статье:

  1. µVision 4.13a -> STM32GPIO_emcu_uV
  2. IAR ARM 6.0 -> STM32GPIO_emcu_iar
  3. IAR ARM 6.21 -> STM32GPIO_emcu_iar_V6.21

Для управления GPIOSTM32 Вы можете применить макросы написанные как альтернативу далеко не оптимальным по мнению многих библиотекам от ST: gpio_emcu.h

Дополнительный материал:

  1. STM32F10xxx Reference manual. Справочное руководство разработчика
  2. STM32F100xx Reference manual. Справочное руководство разработчика
  3. STM32F105xx, STM32F107xx Datasheet
  4. STM32F100x4, STM32F100x6, STM32F100x8, STM32F100xB Data Sheet
  5. Руководство по созданию проектов для STM32DISCOVERY в IAR
  6. Руководство по созданию проектов для STM32DISCOVERY в MDK-ARM, uVision

Другие части

  1. (эта часть) Программирование STM32. Часть 1. GPIO, порты ввода-вывода STM32
  2. Программирование STM32. Часть 2. Система тактирования STM32
  3. Программирование STM32. Часть 3. Система прерываний
  4. Программирование STM32. Часть 4. Внешние прерывания EXTI

Basic таймеры в STM32

Таймеры — это такая периферия контроллера STM32 позволяющая нам очень точно отсчитывать интервалы времени. Это пожалуй одна из самых важных и наиболее используемых функций, однако есть и другие. Следует начать с того, что в контроллерах STM32 существуют таймеры разной степени крутости. Самые простые это Basictimers. Они хороши тем, что очень просто настраиваются и управляются при помощи минимума регистров. Все что они умеют это отсчитывать временные интервалы и генерировать прерывания когда таймер дотикает до заданного значения. Следующая группа (general-purpose timers) гораздо круче первой, они умеют генерировать ШИМ, умеют считать испульсы поступающие на определённые ножки, можно подключать энкодер итд. И самый крутой таймер это advanced-control timer, думаю что его я использовать не буду еще очень долго так как мне пока без надобности управлять трехфазным электродвигателем. Начать знакомство с таймерами следует с чего попроще, я решил взяться за Basic таймеры. Задача которую я себе поставил: Заставить таймер генерить прерывания каждую секунду.

Первым делом отмечу, что Basic таймеры (TIM6 и TIM7) прицеплены к шине APB1, поэтому в случае если частота тактовых импульсов на ней будет меняться, то и таймеры начнут тикать быстрее или медленнее. Если ничего не менять в настройках тактирования и оставить их по умолчанию, то частота APB1 составляет 24МГц при условии что подключен внешний кварц на частоту 8 МГц. Вообще система тактирования у STM32 очень замысловатая и я попробую про неё нормально написать отдельный пост. А пока просто воспользуемся теми настройками тактирования которые задаются кодом автоматически сгенерированым CooCox’ом. Начать стоит с самого главного регистра — TIMx_CNT (здесь и далее x — номер basic таймера 6 или 7). Это счётный 16-ти битный регистр, занимающийся непосредственно счётом времени. Каждый раз когда с шины APB1 приходит тактовый импульс, содержимое этого регистра увеличивается на едницу. Когда регистр переполняется, все начинается с нуля. При нашей дефолтной частоте шины APB1, таймер за одну секунду тикнет 24 млн раз! Это очень дофига, и поэтому у таймера есть предделитель, управлять которым мы можем при помощи регистра TIMx_PSC. Записав в него значение 24000-1 мы заставим счётный регистр TIMx_CNT увеличивать свое значение каждую милисекунду (Частоту APB1 делим на число в регистре предделителе и получаем сколько раз в секунду увеличивается счётчик). Единицу нужно вычесть потому, что если в регистре ноль то это означает, что включен делитель на единицу. Теперь, когда счётный регистр дотикает до 1000 мы можем точно заявить, что прошла ровно одна секунда! И че теперь опрашивать счётный регистр и ждать пока там появится 1000? Это не наш метод, ведь мы можем заюзать прерывания! Но вот беда, прерывание у нас всего одно, и возникает оно когда счётчик обнулится. Для того чтоб счётчик обнулялся досрочно, а не когда дотикает до 0xFFFF, служит регистр TIMx_ARR. Записываем в него то число до которого должен досчитывать регистр TIMx_CNT перед тем как обнулиться. Если мы хотим чтоб прерывание возникало раз в секунду, то нам нужно записать туда 1000. По части непосредственно отсчёта времени это все, но таймер сам по себе тикать не начнет. Его нужно включить установив бит CEN в регистре TIMx_CR1. Этот бит разрешает начать отсчёт, соответственно если его сбросить то отсчет остановится (ваш К.О.).

Таймеры на STM32. Настройка базового таймера

В регистре есть и другие биты но они нам не особо интересны. Зато интересен нам еще один бит, но уже в регистре TIMx_DIER. Называется он UIE, установив его мы разрешаем таймеру генерить прерывания при сбросе счётного регистра. Вот собственно и всё, даже не сложней чем в каких-нибудь AVRках. Итак небольше резюме: Чтоб заюзать basic таймер нужно:

  1. Установить предделитель чтоб таймер не тикал быстро (TIMx_PSC)
  2. Задать предел до которого таймер должен дотикать перед своим сбросом (TIMx_ARR)
  3. Включить отсчет битом CEN в регистре TIMx_CR1 
  4. Включить прерывание по переполнению битом UIE в регистре TIMx_DIER

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

 

#include «stm32f10x.h» #include «stm32f10x_gpio.h» #include «stm32f10x_rcc.h» int main() { GPIO_InitTypeDef PORT; //Включаем порт С и таймер 6 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE); // Настроим ноги со светодиодами на выход PORT.GPIO_Pin = (GPIO_Pin_9 | GPIO_Pin_8); PORT.GPIO_Mode = GPIO_Mode_Out_PP; PORT.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOC, &PORT); TIM6->PSC = 24000 — 1; // Настраиваем делитель что таймер тикал 1000 раз в секунду TIM6->ARR = 1000 ; // Чтоб прерывание случалось раз в секунду TIM6->DIER |= TIM_DIER_UIE; //разрешаем прерывание от таймера TIM6->CR1 |= TIM_CR1_CEN; // Начать отсчёт! NVIC_EnableIRQ(TIM6_DAC_IRQn); //Разрешение TIM6_DAC_IRQn прерывания while(1) { //Программа ничего не делает в пустом цикле } } // Обработчик прерывания TIM6_DAC void TIM6_DAC_IRQHandler(void) { TIM6->SR &= ~TIM_SR_UIF; //Сбрасываем флаг UIF GPIOC->ODR^=(GPIO_Pin_9 | GPIO_Pin_8); //Инвертируем состояние светодиодов }

Стоит добавить небольшое примечание к обработчику прерывания. Дело в том, что он у нас используется сразу двумя блоками периферии: таймером 6 и DAC’ом. Это означает, что если вы будете писать программу которая разрешает прерывания от обоих этих периферийных устройств, то в теле обработчика необходимо проверять кто же из них вызвал прерывание. В нашем случае я не стал этого делать, так как ни каких прерываний от DAC возникнуть не может. Он не настроен, а по дефолту прерывания запрещены. В следующий раз рассмотрим general-purpose таймеры и их практическое применение. 

Генерация ШИМ в STM32

В предыдущей статье про базовые таймеры, мы в очередной раз мигали светодиодами, а в этот раз пойдем гораздо дальше и попробуем вкурить как заставить контроллер STM32 генерировать ШИМ. Для этого нам придётся использовать один из таймеров общего назначения, ведь именно у них есть всё что для этого нужно. Весь остальной  функционал  этих таймеров конечно впечатляет, но в моей практике он пока не пригодился. Хотя возможно, что в будущем мне пригодятся такие полезные фичи как функция подсчёта внешних импульсов и возможность аппаратно обрабатывать повороты энкодера. Но пока займемся ШИМом. Есть вот такая схема из контроллера, трех резисторов и RGB светодиода которым мы будем управлять. Управление заключается в том, чтоб плавно зажечь и погасить каждый цвет. Разумеется можно взять три разных светодиода если нет RGB.

 

 

Мы подключили светодиод к этим выводам не случайно. Таймеры общего назначения могут генерировать ШИМ только на определённых ножках. Поскольку мы будем использовать таймер 2, то в нашем распоряжении есть 4 ноги (PA0-PA3).  Чтоб таймер мог их использовать нужно разрешить это аж в двух местах: Настроить три ноги (PA1-PA3) как выход с альтернативной функцией и разрешить в настройках таймера дергать эти ноги для генерации ШИМа. Для этого нам потребуется регистр CCER

Если установить в единицу один из битов выделенных синим цветом, то таймеру будет позволено использовать для ШИМа соответствующую ногу. Из схемы видно, что нам потребуется установить биты   CC2E, CC3E и CC4E. Теперь нам нужно настроить режим ШИМа: Прямой или инверсный (я не претендую на правильность терминологии). Разница вполне очевидна — при прямом ШИМе чем больше число в регистре сравнения — тем больше коэффициент заполнения ШИМа. В случае инверсного ШИМа все наоборот.  Записали ноль в регистр сравнения — коэффициент заполнения 100%.

Работаем с простыми таймерами STM32 F4 discovery

Для выбора режима используются два регистра CCMR1 и CCMR2:

 

 

 

На настройку каждого канала выделяется аж по 8 бит! Но к счастью нам интересны только три бита OCxM[2:0] которые я отметил синим. То что отмечено серым — это те же самые биты но с другим названием, они используются если канал таймера работает в режиме захвата. Рассматривать все комбинации битов я не буду, так как большинство из них к ШИМу отношения не имеют. Нам потребуются только две комбинации бит: 

 

OCxM2

OCxM1

OCxM0

Режим

1

1

0

Прямой ШИМ

1

1

1

Инверсный ШИМ

 

RGB светодиод у меня с общим катодом и поэтому я использую инверсный ШИМ. Таким образом нам следует установить все три бита OCxM для трех каналов на которых висят светодиоды. И это всё! Настройка ШИМа закончена, теперь нужно только запустить таймер установив бит CEN в регистре CR1. Для управления скважностью просто пишем число от 0x0000 до 0xFFFF в регистры CCRx, где x номер канала. Собственно следующий код реализует то что было задумано в начале этой статьи: Наращивает яркость светодиода а потом снижает её до нуля и переходит к следующему.

Процесс повторяется бесконечно, смотрится красиво 🙂 

#include «stm32f10x.h» #include «stm32f10x_gpio.h» #include «stm32f10x_rcc.h» void delay(void) { volatile uint32_t i; for (i=1; i != 0xF000; i++); } int main() { //Включем порт А RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //Включаем Таймер 2 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); GPIO_InitTypeDef PORT; // Настроим ноги со светодиодами на выход PORT.GPIO_Pin = (GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3); //Будем использовать альтернативный режим а не обычный GPIO PORT.GPIO_Mode = GPIO_Mode_AF_PP; PORT.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOA, &PORT); //Разрешаем таймеру использовать ноги PA1,PA2,PA3 для ШИМа TIM2->CCER |= (TIM_CCER_CC2E|TIM_CCER_CC3E|TIM_CCER_CC4E); // Для всех трех каналов задаем инверсный ШИМ. TIM2->CCMR1|=(TIM_CCMR1_OC2M_0| TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2); TIM2->CCMR2|=(TIM_CCMR2_OC3M_0 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC4M_0 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2); //Запускаем таймер! TIM2->CR1 |= TIM_CR1_CEN; //После этого пишем данные в TIM2->CCRx — и яркость светодиодов меняется uint32_t pwm_arr[]={0,0,6553,13107,19660,26214,32768, 39321,45875,52428,58982,65535}; uint8_t i; while(1) { for (i=1;i<=11;i++) { TIM2->CCR3=pwm_arr[i]; delay(); } for (i=11;i>=1;i—) { TIM2->CCR3=pwm_arr[i]; delay(); } for (i=1;i<=10;i++) { TIM2->CCR2=pwm_arr[i]; delay(); } for (i=11;i>=1;i—) { TIM2->CCR2=pwm_arr[i]; delay(); } for (i=1;i<=10;i++) { TIM2->CCR4=pwm_arr[i]; delay(); } for (i=11;i>=1;i—) { TIM2->CCR4=pwm_arr[i]; delay(); } } }

Надеюсь, что этот код поможет вам запустить ШИМ на STM32 а мне он поможет не забыть то, что я раскуривал почти пол дня. Не сложные вопросы можно писать ниже.

Карманный осциллограф «Лори» на микроконтроллере STM32F103

Максим Керимов
Декабрь 2016 г.

Постановка задачи

Сделать простейший карманный осциллограф с минимальными затратами времени и средств.

Список компонентов

  • Китайский клон платы «Maple Mini» с микроконтроллером STM32F103C8T6.
  • Дисплей 1.8 TFT 128×160 SPI с драйвером ST7735.
  • Пять резисторов и два конденсатора (рис. 3).
  • Линейный регулятор с малым падением напряжения AMS1117-3.3 (по желанию).
  • Щуп-зажим «пинцет» — 2 шт.
  • Кнопка миниатюрная нормально разомкнутая без фиксации, с щелчком.

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

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

7 диапазонов с ценой деления (клетки): 7 µS, 28 µS, 113 µS, 559 µS, 2 mS, 10 mS, 20 mS.
Чувствительность: 0.25 и 1.0 В/дел.
Максимальная амплитуда входного сигнала: 6 В.
Входное сопротивление: 20 kΩ.
Питание: 4 аккумулятора АА.
Потребляемый ток: 80 mA.

Сигнал какой частоты можно увидеть?

Теоретически можно увидеть 477 кГц. Отличить меандр от пилы, теоретически, можно на частотах 350 кГц и ниже. Практически же, более-менее комфортно можно наблюдать сигналы до 200 кГц. Размер клетки: 20 x 20 px.

«Частота развёртки» нашего осциллографа зависит от быстродействия АЦП. В STM32F103 разрядность АЦП фиксирована и равна 12. Это в полтора раза больше, чем нам нужно. В STM32F407, например, разрядность можно уменьшить, что сократит время измерений. Но это уже другая история с другим бюджетом.

Рис. 2. Подключение дисплея.

Рис. 3. Питание и входная цепь.

Делитель напряжения R1-R2 служит для контроля уровня заряда аккумуляторов. В правом верхнем углу экрана — пиктограмма батарейки, как на мобильном телефоне (на фото отсутствует).

Внешний регулятор напряжения нужен не всегда. На плате микроконтроллера есть свой регулятор 3.3 В 100 мА. Если питать дисплей от него, будет греться. На платах другого типа (с большим разъёмом JTAG) стоит как раз AMS1117, для них внешний не нужен. На некоторых дисплеях тоже есть AMS1117 (и перемычка). Решайте сами.

Последовательно с аккумуляторами имеет смысл поставить выключатель питания ПД9-1 или аналогичный.

Если есть желание увеличить размер своего импеданса, на вход можно добавить неинвертирующий повторитель на ОУ, что позволит достичь значения 1 MΩ и более. Питать ОУ следует непосредственно от аккумуляторов напряжением 4.8 — 5.4 В.

 

Принцип действия

Половина текста программы — это всевозможные инициализации. Принцип действия цифрового осциллографа прост и очевиден.

АЦП производит серию непрерывных последовательных измерений уровня сигнала. Полученные значения складываются в память средствами DMA. Каждый раз мы засекаем время и определяем продолжительность серии замеров. Так мы узнаём цену деления оси времени.

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

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

STM32 для начинающих. Урок 3. Таймеры STM32.

Затем всё повторяется.

Для компиляции я использую среду CooCox CoIDE. Не выложил сюда Кокс-проект, поскольку он содержит абсолютные пути к файлам. Проще создать новый, чем править все пути. После создания проекта не забудьте подключить библиотеки: RCC, GPIO, DMA, SPI, TIM, ADC.

 

Прошивал при помощи программатора-отладчика ST-Link V2. Можно и без него, через USB-Serial адаптер.

 

 

 

Использованы материалы:
Проект шотландского мастера Pingumacpenguin
Adafruit Display
STM32 Сохранение данных АЦП с помощью DMA

 

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

Текст программы

  • main.c
  • lcd7735.c — Дисплей и SPI. Инициализация и функции.
  • delay.c — Счётчик: инициализация, функции пауз.
  • ADC.c — АЦП и DMA.
  • font7x15.h — Шрифт.

v. 1.02

STM32 с нуля. Таймеры.

В микроконтроллерах STM32 есть несколько таймеров, способных работать в режиме широтно-импульсной модуляции. Такой функциональностью обладают все таймеры, кроме Basic timers (TIM6 и TIM7).

Приведу пример использования таймера TIM3 в этом режиме.

Всё, этого достаточно для того, чтобы на [В микроконтроллерах STM32 есть несколько таймеров, способных работать в режиме широтно-импульсной модуляции. Такой функциональностью обладают все таймеры, кроме Basic timers (TIM6 и TIM7).

Приведу пример использования таймера TIM3 в этом режиме.

Всё, этого достаточно для того, чтобы на](http://catethysis.ru/stm32-%e2%86%92-%d0%bf%d0%be%d1%80%d1%82%d1%8b-gpio/ “STM32 → Порты GPIO”) появился ШИМ-сигнал. Как это сделано?

Таймер получает тактовые импульсы с шины APB, чья частота в два раза меньше частоты ядра (24МГц в нашем случае) (ссылка на RCC), и они проходят через прескалер, настроенный нами на 10 — т.е. получается 1.2МГц. Таймер настроен на отсчёт 1000 тактов, после которых берёт новое значение из регистра ARR, которое мы не изменяем — т.е. те же 1000, это период ШИМ-сигнала. В начале цикла таймер выводит в выход «1», а спустя 200 тактов сбрасывает в «0» — это скважность ШИМ.

FILED UNDER : IT

Submit a Comment

Must be required * marked fields.

:*
:*