STM Урок 165. Библиотека CMSIS. STM32F1. Бегущие огни



Изучая возможности библиотеки LL и её умение работать на уровне регистров контроллера, мы начали больше понимать в аппаратной организации микроконтроллеров STM.

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

CMSIS (Cortex Microcontroller Software Interface Standard) — библиотека стандарта программного обеспечения микроконтроллеров Cortex, являющаяся независимым от производителя уровнем аппаратной абстракции для серии процессоров Cortex®-M и определяет общие интерфейсы инструментов.

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

Что же подтолкнуло меня на такой, так сказать, подвиг — на использование библиотеки CMSIS. Подтолкнуло многое. Особенно то, что я хотел создать урок по RCC на LL и причём всё у меня получилось, но только автоинициализация, которая генерируется Cube MX, сбрасывает BACKUP регистры причём даже до инициализации самой RTC, что делает не совсем удобным их использование, а именно для использования автономного литиевого источника питания. Можно, конечно, посредством оператора goto исходя из ряда условий обойти автоинициализацию, но, думаю, не только я не люблю костыли.
Вторая мотивация — это то, что очень уж хочется подобраться поближе к регистрам, к из настройкам, а затем уж к самому ядру ARM. Вообще-то, я уже давно к нему подобрался, разработан ряд проектов на ассемблере, думаю, и до него дойдём.

Сегодня мы напишем простой проект, с помощью которого мы «заставим» контроллер помигать 10-ю светодиодами, подключенными к его портам (конечно же с токоограничивающими резисторами).

Контроллер будем по-прежнему использовать самый простой и дешёвый — STM32F103, расположенный на недорогой отладочной плате. Мало того, схему мы тоже не трогаем. Оставим ту же светодиодную планку, подключенную к тем же ножкам, как и при использовании в уроке 144, в котором мы начали знакомство с режимами пониженного энергопотребления. Правда, никакой милиамперметр мы в разрыв подключать не будем и стандартно подключим наш дешёвый программатор к четырём ножкам платы

 

 

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

Как известно, чтобы любая периферия заработала, нужно первым делом включить ей тактирование. Данная задача решается путём включения определённых битов регистра, отвечающего за ту шину, к которой подключена та или иная периферия. Вообще тактированием занимается модуль RCC (Reset & clock control). У данного модуля существует много регистров, но нам нужны именно те, биты которых будут включать тактирование нужных нам видов периферии.

Для начала посмотрим краткую схему архитектуры контроллера, на которой очень хорошо видно, какие виды периферии к какой шине подключены

 

 

Первым делом в нашем проекте мы будем настраивать работу с интерфейсом SWD, посредством которого мы программируем и отлаживаем наш контроллер. Управление SWD происходит в регистре AFIO_MAPR, до которого мы доберёмся чуть позже, следовательно, нам надо включить тактирование AFIO (alternate-function I/O). Так как он подключен к шине APB2, то включением его тактирования занимается вот этот регистр

 

 

Название каждого бита в таких регистрах говорящее. Каждый из них включает, ту периферию, сокращённое название (аббревиатура) которой присутствует в его названии. Поэтому AFIO включит соответствующий бит данного регистра — AFIOEN

 

 

Аналогично в проекте данного урока мы задействуем тактирование портов GPIOA и GPIOB установкой соответствующих битов в данном регистре

 

 

Теперь посмотрим, собственно, регистр, с помощью определённых битов которого мы задействуем SWD — AFIO_MAPR

 

 

Этот регистр занимается переназначением ножек портов, задействованных для тех или иных видов периферии, а также конфигурирует отладочный последовательный порт. Нам нужно будет его второе назначение. За это отвечает данное битовое поле

SWJ_CFG[2:0] (Serial wire JTAG configuration): битовое поле настройки отладочного последовательного порта. Биты данного поля работают только на запись, считывать мы их не можем. Они используются для настройки SWJ и отслеживания альтернативных функций ввода / вывода. SWJ (Serial Wire JTAG) поддерживает доступ JTAG или SWD к порту отладки Cortex®. Состояние по умолчанию после сброса — SWJ ON без трассировки.

Имеют место вот такие комбинации данных настроек

000: Полный SWJ (JTAG-DP + SW-DP) с ножкой RESET

001: Полный SWJ (JTAG-DP + SW-DP), но без NJTRST

010: JTAG-DP отключен, SW-DP включен.

100: JTAG-DP отключен и SW-DP отключен.

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

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

Для каждого порта ввода-вывода, имеющегося в контроллере, существует по 7 регистров.

Первые два регистра являются частями регистровой пары настройки ножек портов. Так как ножек у портов всего 16 и для каждой ножки отведено по 4 бита для настройки, то получается, что нам требуется всего 64 бита, вот и получилось 2 регистра — младший и старший.

 

Первый регистр — младший, который отвечает за настройку ножек портов от 0 до 7

 

 

Второй регистр — старший, который отвечает за настройку ножек портов от 8 до 15

 

 

 

Теперь рассмотрим назначение битов битовых полей данных регистров для отдельно взятой ножки

 

CNFy[1:0] (Port x configuration bits): битовое поле настройки ножки y порта x.

В случае использования ножки на вход:

00: Аналоговый режим

01: Плавающий вход (состояние после сброса) без подтяжки

10: Вход с подтягиванием резистора или к плюсу или к общей шине (завит от состояния бита в регистре ODR)

11: не используется

В случае использования ножки на выход:

00: Обычный двухтактный выход

01: Выход с открытым коллектором

10: Альтернативная функция, двухтактный выход

11: Альтернативная функция, выход с открытым коллектором.

 

MODEy[1:0] (Port x mode bits): битовое поле настройки режима работы ножки y порта x.

00: Режим входа (состояние после сброса)

01: Режим выхода, максимальная скорость 10 MHz.

10: Режим выхода, максимальная скорость 2 MHz.

11: Режим выхода, максимальная скорость 50 MHz.

Следующий регистр — регистр входных данных

 

 

Каждый бит данного регистра содержит состояние соответствующей ножки порта. Доступен только для чтения.

Следующий регистр — регистр выходных данных

 

 

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

В режиме входа ножки мы устанавливаем направление подтяжки резистора

0 — резистор подтянут к общему проводу,

1 — резистор подтянут к шине питания.

Следующий регистр — специальный регистр атомарной установки или сброса ножек порта

 

 

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

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

Другими словами, битовое поле BRy, где y — номер ножки порта, работает следующим образом

0 — нет эффекта

1 — сброс ножки порта в низкий уровень.

А битовое поле BSy, где также y — номер ножки порта, работает следующим образом

0 — нет эффекта

1 — установка на ножке порта высокого уровня.

 

Следующий регистр — регистр сброса ножек порта

 

 

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

Следующий регистр — регистр защиты конфигурации порта

 

 

Биты данного регистра предназначены для защиты ножек портов от изменения. Если мы хотим защитить какие-то ножки от записи, то мы записываем в соответствующие им биты данного регистра, находящиеся в младшем его полуслове, единицы, остальные биты оставляем в нулях. Затем при помощи специальной комбинации над битом 16 — LCKK мы производим применение блокировки. Данная последовательность следующая: записываем в данный бит 1, затем 0, затем опять 1, затем данный бит считываем дважды, первый раз должен считаться 1, второй — 0. Если всё так, то значит мы защитили нужные ножки.

Также стоит сказать, что у микроконтроллеров высших моделей (F4, F7 и т.д.) регистры для настройки портов другие. Мы помним наши первые уроки по STM32, так как в них мы работали с CMSIS, но использовали мы контроллер STM32F407VG, установленный на плате Discovery 4, там регистры были другие. Об этом надо помнить, то есть переносимость кода на CMSIS на другие контроллеры не всегда имеет место. Также мы перенастраивали порты, когда работали с датчиком DS18B20 уже на контроллере F103.

Пока всё по аппаратной части.

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

Cube MX мы никакой не запускаем, запускаем сразу Keil и создаём в нём новый проект. Как это делать, мы знаем, но в видеоверсии я это всё равно покажу.

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

 

 

 

Затем выберем следующие пункты

 

 

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

 

 

Создадим в данной группе новый файл main.c, сохранив его в папку с проектом, желательно во вложенной в нём папке с таким же именем, чтоб потом не запутаться

 

 

Прежде чем начнём писать код, давайте немного настроим наш проект.

Зайдём в его настройки и во вкладке Target изменим частоту тактирования

 

 

Установим галку вот здесь в следующей вкладке, может пригодится HEX-файл

 

 

Также можно настроить и формирование чистого бинарника — файла BIN.

Для этого во вкладке User вот здесь напишем вот такую строку «fromelf —bin —output .\Objects\BLINK01_CMSIS.bin .\Objects\BLINK01_CMSIS.axf» (кавычки не нужны)

 

 

В следующей вкладке напишем вот это «STM32F103xB,STM32F10X_MD» (без кавычек), убедившись также в том, что уровень оптимизации — 0

 

 

Сохраним настройки, нажав кнопку OK, затем откроем их снова, т.к. дальнейшие настройки зависят от уже проделанных.

Во вкладке Debug выберем программатор

 

 

Нажмём кнопку Setting рядом со строкой выбора отладчика и, попав в его настройки в закладке и в следующем пункте изменим значение, также убедимся, что остальные настройки такие же как в скриншоте

 

 

Перейдём в третью вкладку и настроим программатор на автоперезагрузку контроллера после прошивки

 

 

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

Теперь откроем файл main.c и подключим в нём для начала файл макросов для нашего контроллера

 

 

Далее добавим несколько удобных макросов для наших светодиодов

 

 

В наших макросах мы в свою очередь используем макросы CLEAR_BIT и SET_BIT, которые очень удобны и используются вместо стандартных присвоений с операторами И и ИЛИ. Это именно макросы, а не функции, поэтому для их работы компилятор не создаёт отдельных процедур, всё встраивается в код.

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

 

 

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

 

 

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

Далее добавим функцию main(), в которой пока только включим тактирование AFIO и добавим пустой бесконечный цикл

 

 

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

Продолжаем наполнять кодом тело функции main(), в котором немного подождём после включения тактирования шины. Делается это посредством считывания регистра куда-никуда

 

 

Здесь мы используем ещё один макрос — READ_BIT.

Настроим наш SWD

 

 

Аналогичным образом включим тактирование нужных нам портов

 

 

Теперь настроим режимы работы ножек портов. Регистры мы эти с назначением их битов теперь знаем

 

 

Здесь мы использовали ещё один макрос — MODIFY_REG, который сбрасывает биты, которые включены во втором параметре макроса и устанавливает биты, которые включены в третьем. В первом у нас сам регистр.

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

 

 

Вот и весь наш код. В данном уроке получилось больше теории. Но что поделать! Без неё никуда. Иначе мы бы вообще ничего не написали.

Соберём наш код, прошьём контроллер.

А вот и результат

 

 

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

Всем спасибо за внимание!

 

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

 

 

STM Библиотека CMSIS. STM32F1. Бегущие огни

22 комментария на “STM Урок 165. Библиотека CMSIS. STM32F1. Бегущие огни
  1. megger380:

    Да… Это не AVR-ка… Спасибо! Ждём продолжений.

  2. Roman:

    Через еще 150 уроков будем на перфокартах учиться прогать / мигать светодиодом? По CMSIS в интернете МОРЕ инфы 3-15 летней давности, сотни превосходных уроков других адептов МК. Про ассемблер вообще не понятно — зачем на нем сейчас писать? Вы не напишите большой проект на асме лучше чем скомпилирует современный компилятор из си, а для мигания светодиодом не все ли равно на чем писать? Уникальность ВАШЕГО проекта заключается как раз в использовании СОВРЕМЕННОГО инструмента CubeMX (библиотек HAL и LL), у которого хоть и есть недостатки, но они очень быстро исчезают(вспомните его 3 года назад и сейчас). В общем рекомендую вам задуматься в какую сторону вы идете, есть миллион интересных еще нераскрытых/плохо раскрытых тем, по которым можно сделать еще тысячи уроков, а можно переписывать уже по 100 раз переписанные статьи про то как мигать светодиодами при помощи CMSIS. Решать конечно же вам.

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

    • Triger:

      Полностью с вами согласен … Понятно что администратору ресурса лучше знать в каком направлении двигаться но лично мне как начинающему осваивать ARM неинтересно вникать в древний CMSIS … потратить вагон времени а потом все равно вернуться к Кубу и еже с ним…

      • Думаю, что все равно нужно знать железо, под которое пишешь программу, иначе будет непонятно, что творится и всё будет хорошо только в идеальном случае, а вот если посыпятся ошибки, то будет намного сложнее, чем если когда знаешь всё изнутри. Это по своему 30-летнему опыту знаю, поэтому не хочу, чтобы на мои грабли наступил ещё кто-то. А LL ещё будет, готовятся уроки по ADC.

    • Сергей:

      Да ладно. Всё правильно.CMSIS жил,CMSIS-жив,CMSIS-будет вечно жить.Вот вы на кубе и напишите «море» разных проектов

    • Mikhail:

      Не надо утрировать. Это я про перфокарты.
      Про ассемблер: знать его очень даже нужно и полезно. Конечно, большой проект на нём вздумает писать разве что отъявленный мазохист. Но делать оптимизацию скорости и объёма кода — первый инструмент. Так что высказанные замечания по поводу неприемлемости ассемблера и превосходности CubeMX я считаю, мягко говоря, неуместными и незрелыми.

  3. ED:

    Круто, что начали делать уроки с библиотекой CMSIS. Ближе к телу и пониманию работы ядра и периферии. Все эти генераторы проектов на самом деле делают медвежью услугу в итоге.

  4. У
    меня вопрос к учителу.GPIOA->CRL, GPIO_CRL_CNF7 . GPIO_CRL_MODE7_0 | GPIO_CRL_MODE6_0 вот регистр CRL
    В случае использования ножки на выход:

    00: Обычный двухтактный выход

    01: Выход с открытым коллектором

    10: Альтернативная функция, двухтактный выход

    11: Альтернативная функция, выход с открытым коллектором
    Это CNF бит.какой код у вас запишится? а как писать 00 .01 .10 .11.да и бите MODE? Вот этот вопрос я могу задать любому спецу?Я аматор.

    • -=Drakon=-:

      Открываем stm32f10x.h и поиском ищем в нем GPIO_CRL_MODE

    • Сергей:

      «Здесь мы использовали ещё один макрос — MODIFY_REG, который сбрасывает биты, которые включены во втором параметре макроса и устанавливает биты, которые включены в третьем. В первом у нас сам регистр.» Вот эта фраза Вас не наталкивает на ответ.

  5. Gennadiy:

    У меня так же вызывают интерес ваши уроки с применением CMSIS.
    Большое спасибо.

  6. Сергей:

    Наконец то CMSIS.Ассемблер, это уже для ниндзя. 3 тысячелетие однако. АСМ можно забывать потихоньку….хотя…может и не надо.

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

  7. Александр:

    «В следующей вкладке напишем вот это «STM32F103xB,STM32F10X_MD» (без кавычек)»

    А если у меня другой контроллер, где мне взять эту строчку для моей модели?

  8. Микола:

    tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_AFIOEN); зачем нужна задкржка после включения тактирования периферии?

    • Скорее всего, для уверенности, что бит установился. Во всех официальных библиотеках есть. Например, в LL.

      • Микола:

        Обратил внимание на описание регистра RCC_CFGR. «Clock co nfiguration register (RCC_CFGR)
        Address offset: 0x04
        Reset value: 0x0000 0000
        Access: 0 ≤ wait state ≤ 2, word, half-word and byte access
        1 or 2 wait states inserted only if the access occurs during clock source switch.»
        Задержка от 1 до 2 команд рекомендуется самим производителем МК.

  9. Виталий:

    Не подскажете какой-нибудь справочник по макросам и функциям CMSIS? Откуда брать все эти названия: __forceinline, SET_BIT и т.д.?

  10. Виталий:

    В Keil V5.31.0.0 этот код не компилируется. Варнинги на все переменные типа «main.c(6): warning: no previous extern declaration for non-static variable 'tmpreg' [-Wmissing-variable-declarations]», ошибка «main.c(9): error: unknown type name '__forceinline'». Чтобы всё исправить нужно в настройках Options for target на вкладке Target в списке ARM Compiler выбрать «Use default compiler version 5» вместо «Use default compiler version 6».
    Как теперь с этим жить?

  11. Антон:

    У меня такой же вопрос. Что значат B и MD?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*