Avrstudio 4. cmsis для avr. структура для gpio

Микроконтроллер AVR и GPIO. Порты ввода-вывода.

Долгое время мы оставляли без внимания микроконтроллеры AVR, и вот пришла пора исправить это недоразумение! Как и для других контроллеров, будем постепенно рассматривать различную периферию AVR’ок, сначала теорию, регистры, ну и под конец небольшие примеры.

В качестве IDE я использую AVR Studio 5, шестую версию AVR Studio даже не пробовал, не так часто последнее время мне попадаются задачи для AVR. А вообще неплохо иметь еще и установленную AVR Studio 4, потому что порой случается так, что запрограммировать контроллер из AVR Studio 5 не представляется возможным. Вот совсем недавно я хотел прошить ATMega2560 при помощи программатора STK500 и это оказалось неосуществимо через 5-ую студию… Хорошо осталась со старых времен AVR Studio 4, и проблема решилась в течение пары минут.

Что тут еще можно сказать?… Да в принципе, это все, можно переходить к делу!

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

На входе каждой ножки микроконтроллера заботливые разработчики поставили пару диодов, которые должны спасти микроконтроллер в случае превышения допустимого напряжения. НО! На деле все не столь радужно, и если подать на вход микроконтроллера, например, 7.5 В, то контроллеру уже никто и ничто не поможет, проверено на собственном опыте. Поэтому все эксперименты нужно проводить аккуратно 😉

Теперь к регистрам. Вся работа с портами ввода-вывода в AVR’ках сосредоточена в трех регистрах – DDRx, PORTx, PINx. Символ «x» заменяется соответствующим названием порта (A, B…). То есть, если мы хотим работать с портом A микроконтроллера, то нам нужны регистры DDRA, PORTA, PINA. Если мы хотим работать с пятым пином порта А (РА5), то нас интересует пятый бит упомянутых выше регистров. Как видите, все довольно просто, осталось лишь разобраться, что же и куда надо записывать, за что отвечает каждый из этих регистров. Итак, начинаем…

Регистр DDRx.

DDRx отвечает за направление работы соответствующих выводов микроконтроллера. Каждый пин может быть либо входом, либо выходом, третьего не дано. Для того, чтобы настроить вывод на работу в режиме входа в регистр DDR для этого порта нужно записать 0, для выхода – 1. Пусть нам нужно настроить РА6 как вход, а РА3 как выход. Что делаем? Верно, 3-й бит регистра DDRA выставляем в 1, а в 6-й бит все того же регистра DDRA записываем 0.

Регистр PINx.

В этот регистр мы записать ничего не можем, он предназначен исключительно для чтения данных. В этом регистре информация об уровне сигнала на соответствующем порте. Как мы помним, микроконтроллер – это цифровое устройство, и сигналы на его ножках могут иметь либо высокий уровень (логическая 1), либо низкий (логический 0). Если мы хотим узнать, что у нас там на входе РВ4, то нас интересует 4-й бит регистра PINB.

Регистр PORTx.

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

  • PORTx = 1 – при такой конфигурации мы получаем вход с подтяжкой вверх (Pull Up)
  • PORTX = 0 – высокоимпедансный вход (Hi-Z) – это значит, что сопротивление порта очень велико.

Итак, продолжаем с регистром PORTx. Если вывод работает в качестве выхода, а в регистре PORTx единица, то на выводе будет высокий уровень сигнала, аналогично, PORTx = 0 – низкий уровень.

Давайте рассмотрим небольшой пример для иллюстрации всего сказанного. Пусть требуется настроить вывод РС4 на работу в режиме входа с подтяжкой вверх. Для этого нам надо:

  • в 4-й бит регистра DDRC записать 0 (режим входа)
  • в регистре PORTC 4-й бит выставить в 1 (подтяжка вверх).

В принципе это все, что касается теории, углубляться дальше не будем. Осталось соблюсти традиции и поиграть с диодиком. Пусть диод подключен к выводу PВ5. Заставим его помигать! И прежде всего создадим проект. Я, как уже говорил, использую AVR Studio 5, а в качестве контроллера выберу мой любимый ATMega88 🙂

Вот таким получилось наше первое общение с микроконтроллерами AVR. В скором времени рассмотрим по возможности всю их периферию, а потом можно будет придумать что-нибудь интересное!

STM Урок 169. CMSIS. STM32F1. GPIO. Input

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

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

Какие регистры и какие их биты отвечают за те или иные настройки GPIO, мы также знаем из того же урока, поэтому можно уже приступать и к проекту.

Проект был сделан из проекта прошлого урока с именем CMSIS_TIM2 и назван был CMSIS_BUTTON01.

Откроем наш проект в Keil и в файле main.c из обработчика таймера мы пока удалим вот этот код, оставив только сброс флага прерывания, так как таймер этот нам ещё пригодится в данном уроке

case 0: LED10_OFF(); LED1_ON(); break ;

case 1: LED1_OFF(); LED2_ON(); break ;

case 2: LED2_OFF(); LED3_ON(); break ;

case 3: LED3_OFF(); LED4_ON(); break ;

case 4: LED4_OFF(); LED5_ON(); break ;

case 5: LED5_OFF(); LED6_ON(); break ;

case 6: LED6_OFF(); LED7_ON(); break ;

case 7: LED7_OFF(); LED8_ON(); break ;

case 8: LED8_OFF(); LED9_ON(); break ;

case 9: LED9_OFF(); LED10_ON(); break ;

if (tim2_count>9) tim2_count=0;

Прежде чем следить за состоянием ножки, её нужно настроить.

Порт A у нас уже тактируется, поэтому включать тактирование нам не надо.

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

Для этого в битовом поле CNF1[1:0], отвечающего за настройку именно ножки 1 порта A, регистра GPIOA_CRL мы установим вот такую комбинацию битов

Таким образом мы настроим подтягивание резистора. А куда именно он подтянется, – к общему проводу или к питанию, будет зависеть уже от настройки соответствующего бита регистра ODR.

А в битовом поле MODE1[1:0] мы выберем самую первую комбинацию битов

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

MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF7 | GPIO_CRL_CNF6 | GPIO_CRL_CNF5 |

GPIO_CRL_CNF4 | GPIO_CRL_CNF3 | GPIO_CRL_CNF2 | GPIO_CRL_CNF1_0 |

GPIO_CRL_MODE1,

GPIO_CRL_CNF1_1 | GPIO_CRL_MODE7_0 | GPIO_CRL_MODE6_0 | GPIO_CRL_MODE5_0 |

GPIO_CRL_MODE4_0 | GPIO_CRL_MODE3_0 | GPIO_CRL_MODE2_0);

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

Настроим бит 1 регистра ODR, чтобы резистор подтянулся именно к питанию

Удалим старт нашего таймера

В бесконечный цикл функции main() добавим условие, в тело которого мы будем попадать при условии, когда на ножке PA1 установится уровень 0, то есть будет нажата кнопка. В данном теле мы зажжём первый светодиод

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

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

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

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

Для этого в функции TIM2_Init внесём изменения в коде

WRITE_REG(TIM2->ARR, 50);

Данный период срабатывания подбирается опытным путём. Теперь таймер будет у нас срабатывать каждые 2.5 милисекунды. Думаю, нормально.

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

ОБОРУДОВАНИЕ
ТЕХНОЛОГИИ
РАЗРАБОТКИ

Блог технической поддержки моих разработок

Урок 7. Управление портами ввода-вывода через регистры CMSIS.

Научимся управлять портами через прямое обращение к регистрам.

В уроке 4 я писал, что главное преимущество прямого обращения к регистрам это скорость выполнения операций. Как удачный вариант я отметил способ, при котором начальная конфигурация формируется с помощью STM32CubeMX и библиотеки HAL, а данные в ходе выполнения программы передаются через регистры CMSIS. Начальные установки редко приходится менять при выполнении программы.

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

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

Для примера мы собираемся установить режимы работы:

  • для вывода PB12 – вход с подтягивающим резистором к шине питания;
  • для вывода PB13 – активный выход.

Позже подключим к этим выводам кнопку и светодиод, напишем управляющую программу.

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

Прежде всего, разрешить работу порта в регистре Разрешения тактирования периферийных устройств шины APB2 (APB2 peripheral clock enable register) RCC_APB2ENR. За порт B отвечает бит IOPBEN. Установим его в 1.

Читайте также  Дистанционное управление на инфракрасных лучах

В принципе, эту операцию уже сделал за нас конфигуратор STM32CubeMX, когда мы выбрали активными выводы порта B.

Биты конфигурации и режима для вывода PB12 расположены в регистре:

GPIOB_CRH, биты CNF12[1:0] и MODE12[1:0].

Смотрим по таблицам режима и конфигурации портов. Надо установить состояние:

CNF12[1:0], MODE12[1:0] = 10, 00 (режим вход).

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

В IDE открываем файл stm32f103xb.h. Он содержит имена регистров микроконтроллера.

Структура для регистров портов в нем описана так.

typedef struct
<

__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
> GPIO_TypeDef;

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

Мы должны установить в регистре GPIOB_CRH, биты 19-16 в состояние 1000. Можно просто загрузить в регистр данное.

В этом случае мы сбросим все остальные биты. Что-то перестанет работать. Но если в загружаемом числе сформировать все биты этого регистра сразу, то способ вполне допустимый. Более того это самый быстрый и короткий вариант.

Если мы хотим затронуть только нужные биты, то можно сделать так.

GPIOB->CRH &= 0xfff0ffff; // сбрасываем биты 16-19
GPIOB->CRH |= 0x00080000; // устанавливаем биты

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

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

( 0b1111 сбрасываем биты 16-19
GPIOB->CRH |= 0b1000 устанавливаем биты

Все вычисления с константами компилятор сделает на этапе трансляции и в правой части операций получатся точно такие числа, как и в предыдущем варианте.

В файле stm32f103xb.h есть имена и для битов регистра GPIOx_CRH. Вот, что касается 12го разряда.

/******************* Bit definition for GPIO_CRH register *******************/
#define GPIO_CRH_MODE12_Pos (16U)
#define GPIO_CRH_MODE12_Msk (0x3U /*!
#define GPIO_CRH_MODE12 GPIO_CRH_MODE12_Msk /*!
#define GPIO_CRH_MODE12_0 (0x1U /*!
#define GPIO_CRH_MODE12_1 (0x2U /*!

#define
GPIO_CRH_CNF12_Pos (18U)

#define GPIO_CRH_CNF12_Msk (0x3U /*!
#define GPIO_CRH_CNF12 GPIO_CRH_CNF12_Msk /*!
#define GPIO_CRH_CNF12_0 (0x1U /*!
#define GPIO_CRH_CNF12_1 (0x2U /*!

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

(GPIO_CRH_CNF12 | GPIO_CRH_MODE12); // сбрасываем биты полей CNF и MODE
GPIOB->CRH |= (0b10 // устанавливаем биты

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

Установим таким способом вывод PB13 в режим выхода. Для этого надо в поля CNF13[1:0], MODE13[1:0] установить = 00, 10 (режим выход).

(GPIO_CRH_CNF13 | GPIO_CRH_MODE13); // сбрасываем биты полей CNF и MODE
GPIOB->CRH |= (0b00 // устанавливаем биты

Теперь попробуем считать и установить состояния выводов. Давайте подключим к выводу PB12 кнопку, а к выводу PB13 светодиод.

И напишем программу, которая управляет светодиодом с помощью кнопки. Кнопка нажата – светодиод светится, отжата – светодиод погашен.

Считать состояние вывода можно через регистр ввода данных.

В структуре GPIO_TypeDef это регистр IDR. Проверяем 12й бит.

if( (GPIOB->IDR & (1
// бит 12 = 0, кнопка нажата

>
else <
// бит 12 = 1, кнопка отжата

Для бита 12 регистра IDR есть имя, но оно не сильно улучшает читаемость программы.

/*!
#define GPIO_IDR_IDR12_Pos (12U)
#define GPIO_IDR_IDR12_Msk (0x1U /*!
#define GPIO_IDR_IDR12 GPIO_IDR_IDR12_Msk /*!

Используя имя бита можно написать:

if( (GPIOB->IDR & GPIO_IDR_IDR12) == 0 ) <

Теперь об установке логического состояния выхода. Есть несколько вариантов.

Первый – через регистр вывода.

Установка бита 13:

Вставляем в логические блоки включение и выключение светодиода.

if( (GPIOB->IDR & GPIO_IDR_IDR12) == 0 ) <
// бит 12 = 0, кнопка нажата
GPIOB->ODR &=

GPIO_ODR_ODR13; // зажечь светодиод
>
else <
// бит 12 = 1, кнопка отжата
GPIOB->ODR |= GPIO_ODR_ODR13; // погасить светодиод
>

И всю эту конструкцию в цикл while(1) в файле main.c.

Компилируем, загружаем, проверяем. У меня работает.

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

Если нам надо установить вывод PB13 в 1, то:

GPIOB -> BSRR = GPIO_BSRR_BS13; // установка бита

Для сброса бита надо выполнить команду:

GPIOB -> BSRR = GPIO_BSRR_BR13; // сброс бита

Можно сбросить или установить сразу несколько битов одним обращением к регистру BSRR. Это будет выглядеть так.

GPIOB -> BSRR = GPIO_BSRR_BS10 | GPIO_BSRR_BS15 | GPIO_BSRR_BR11 | GPIO_BSRR_BR12; // установить биты 10 и 15, сбросить биты 11 и 12

Проверяем в программе.

if( (GPIOB->IDR & GPIO_IDR_IDR12) == 0 ) <
// бит 12 = 0, кнопка нажата
// GPIOB->ODR &=

GPIO_ODR_ODR13; // зажечь светодиод
GPIOB -> BSRR = GPIO_BSRR_BR13; // сброс бита
>
else <
// бит 12 = 1, кнопка отжата
// GPIOB->ODR |= GPIO_ODR_ODR13; // погасить светодиод
GPIOB -> BSRR = GPIO_BSRR_BS13; // установка бита
>

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

GPIOB -> BSRR = GPIO_BSRR_BS12; // подтягивающий резистор к + 3 В

Добавим эту строку в блок начальной конфигурации портов.

С регистром защиты LCKR разберетесь сами. Установка и сброс битов:

GPIOB -> LCKR |= GPIO_LCKR_LCK12 | GPIO_LCKR_LCK13;
GPIOB -> LCKR &=

(GPIO_LCKR_LCK110 | GPIO_LCKR_LCK11);
GPIOB -> LCKR |= GPIO_LCKR_LCKK;
GPIOB -> LCKR &=

Загрузить проект полностью можно по ссылке:

Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!

Многим покажется, что работать с портами на STM32 очень сложно. Особенно если сравнивать c аналогичными операциями в системе Ардуино. Но на Ардуино мы используем не прямое обращение, а функции. В следующем уроке будем работать с функциями управления портами на STM32. Все будет намного проще.

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

В следующем уроке будем управлять портами STM32 с помощью библиотеки HAL.

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

В этой части мы разберемся с порами ввода-вывода GPIO микроконтроллера STM32F103C8 и напишем «Hello, World!» с мигающим светодиодом, а так же научимся читать состояние выводов микроконтроллера и использовать встроенный подтягивающий резистор. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: http://dimoon.ru/category/obuchalka/stm32f1.

Введение

General-purpose input/output (GPIO) — важный компонент любого микроконтроллера, с помощью которого он взаимодействует с окружающим миром. В микроконтроллерах STM32 порты именуются буквами A, B, C и так далее: GPIOA, GPIOB, GPIOC... Каждый GPIO имеет 16 линий ввода/вывода, причем каждая линия может быть настроена независимо от других. Вот варианты настройки:

  • Input floating — вход с отключенными подтягивающими резисторами
  • Input pull-up — вход с подтяжкой к логической единице
  • Input-pull-down — вход с подтяжкой к логическому нулю
  • Analog — аналоговый вход (например, для АЦП)
  • Output open-drain — выход с открытым коллектором (записали 1 — выход в высокоимпедансном состоянии, записали 0 — выход прижат внутренним транзистором к земле)
  • Output push-pull — выход «тяни-толкай» (записали 1 — на выходе лог. 1, записали 0 — на выходе лог. 0)
  • Alternate function push-pull — альтернативная функция в режиме «тяни-толкай»
  • Alternate function open-drain — альтернативная функция в режиме открытого коллектора

Дам несколько пояснений по поводу режимов.

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

Alternate function. В этом режиме ножкой микроконтроллера управляет внутренняя цифровая периферия, например, модуль USART.

Регистры GPIO

Давайте рассмотрим регистры портов GPIO.

Port configuration register low (GPIOx_CRL)

Рис. 1. Регистр CRL

Это конфигурационный регистр для выводов порта с номерами от 0 до 7. Каждому выводу предоставлено 4-ре бита конфигурации: 2 бита MODEy и 2 бита CNFy.

MODEy[1:0]: Режим ножки порта, вход или выход. В режиме выхода нужно выбрать максимальную частоту переключения данной ножки, насколько понял это является оптимизацией энергопотребления порта.

  • 00: Вход (значение после сброса)
  • 01: Выход, максимальная частота 10 MHz.
  • 10: Выход, максимальная частота 2 MHz.
  • 11: Выход, максимальная частота 50 MHz.

CNFy[1:0]: Конфигурация режима.

В режиме входа (MODEy[1:0]=00):

  • 00: Analog mode — аналоговый режим (подключен к АЦП или ЦАП-у)
  • 01: Floating input — вход с отключенными подтягивающими резисторами (значение после сброса)
  • 10: Input with pull-up / pull-down — вход с подтяжкой вверх или вниз
  • 11: Reserved — не используется

В режиме выхода (MODEy[1:0]>00):

  • 00: General purpose output push-pull — выход в режиме тяни/толкай
  • 01: General purpose output Open-drain — выход с открытым коллектором
  • 10: Alternate function output Push-pull — выход альтернативной функции режиме тяни/толкай
  • 11: Alternate function output Open-drain — выход альтернативной функции с открытым коллектором

Port configuration register high (GPIOx_CRH)

Рис. 2. Регистр CRH

Это конфигурационный регистр для выводов порта с номерами от 8 до 15. Тут все то же, что и для регистра GPIOx_CRL.

Port input data register (GPIOx_IDR)

Рис. 3. Регистр IDR

IDRy: в этих битах содержится входное значение соответствующего порта ввода-вывода.

Port output data register (GPIOx_ODR)

Рис. 4. Регистр ODR

ODRy: выходные данные порта.

Port bit set/reset register (GPIOx_BSRR)

Рис. 5. Регистр BSRR

С помощью этого регистра можно сбросить или установить любой бит регистра ODR без операций чтение-модификация-запись.

BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Сбрасывает в ноль соответствующий бит ODRx

BSy: Установить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Устанавливает в единицу соответствующий бит ODRx

Port bit reset register (GPIOx_BRR)

Рис. 6. Регистр BRR

С помощью этого регистра можно сбросить любой бит регистра ODR без операций чтение-модификация-запись.

BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Сбрасывает в ноль соответствующий бит ODRx

Port configuration lock register (GPIOx_LCKR)

Рис. 7. Регистр LCKR

Этот регистр используется для блокировки конфигурационных битов порта после записи корректной последовательности в 16 бит (LCKK) регистра. Значения битов [15:0] используется для блокировки конфигурации GPIO. Во время блокирующей последовательности в LCKK значения LCKR [15: 0] не должны меняться. Когда блокирующая последовательность была записана, конфигурация выбранных портов ввода/вывода может быть изменена только после сброса микроконтроллера. Каждый LCKy бит блокирует возможность изменения четырех битов конфигурации порта (CRL, CRH).

LCKK[16]: Ключ блокировки.

  • 0: Блокировка конфигурации порта не активна.
  • 1: Блокировка конфигурации порта активна. GPIOx_LCKR заблокирован до следующего сброса микроконтроллера.

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

  • Записать 1
  • Записать 0
  • Записать 1
  • Прочитать 0
  • Прочитать 1 (эта операция чтения не является обязательной, а всего лишь подтверждает успешность установки блокировки)

LCKy: Эти биты могут быть прочитаны и записаны, но запись можно произвести только если бит LCKK равен нулю.

  • 0: Конфигурация пина номер y не заблокирована
  • 1: Конфигурация пина номер y заблокирована

Настройка порта 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. Создадим функцию инициализации порта:

Первым делом включаем тактирование порта GPIOB:

Далее настройка порта. Нам нужен пин PB12. Его конфигурационные биты находятся в регистре CRH:

Все Настройка завершена! Вот полный код функции:

Теперь управление. Для этого нам понадобится регистр ODR (рис. 4). Вот функция установки высокого уровня на PB12:

Ни чего сложного, почти как на AVR-ках! Однако, у нас все же более серьезный микроконтроллер, и у него есть некоторые фишки. Выражения вида GPIOB->ODR |= (1 Метки: CMSIS, IAR, STM32, Обучалка. Закладка Постоянная ссылка.

36 комментариев: Программирование STM32. Часть 5: Порты ввода-вывода GPIO

А почему мы не используем регистр BSRR для записи нуля а пользуемся BBR? Из тех же соображений что и ODR. И что будет если в него в двух позициях записать 1 и на установку 1 и на установку 0

Можно и BBR, разницы ни какой нет. Можно использовать и ODR, но в этом случае нужно выполнять операцию типа Чтение-Модификация-Запись, а для BSRR и BBR только запись — это быстрее и нет возможности возникновения нарушения атомарности операции

Что будет, если в BSRR записать единицы на установку и сброс одного и того же пина GPIO я без понятия, надо попробовать)) В документации такого вроде не попадалось

Нашел в даташите: If both BSx and BRx are set, BSx has priority.

Здравствуйте. у меня вопрос — в проекте для задержки между вкл. и выкл. светодиода используется цикл for.а как обстоят дела в стм32 насчет служебных ф-ий типа Delay_ms() как в GCC для 8-ми битных АВРок?

Сам использую компилятор от IAR, насколько знаю, для ARM-ов ни чего подобного нет, хотя для тех же AVR-ок есть функция __delay_cycles(). В реальных проектах если надо генерить сигнал с периодом больше 1ms и с не очень большой точностью поддержания периода, то реализую это на программных таймерах и конечных автоматах, если периоды меньше и/или нужна большая точность — то аппаратные таймеры. Если же нужно выдать короткий управляющий импульс для какой-то микры на плате — то использую задержку на for, что-то типа for(i=0; i Den пишет:

Нашел на просторах инета такой интересный код

Никаких таймеров, все быстро и компактно. Только у меня в include надо core_cm3.h вместо stm32f10x_tim.h. Использую STM32CubeIDE

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

Здравствуйте. еще вопросик — например я затактировался от HSE через PLL x9. в итоге частота 72 Мгц. Далее например подключаю GPIOB : RCC->APB2ENR|=RCC_APB2ENR_IOPBEN; .Но ведь выход порта В я могу настроить только на макс. 50Мгц , Значит ли это что и делитель АРВ2 в этом случае настроить как минимум на HCLK/2 поскольку частота на этой шине 72 Мгц?

То, что настраивается в регистрах GPIO как максимальная скорость работы, как ни странно, не имеет ни какого отношения к тактированию. Этими битами настраивается максимальный потребляемый ток данного пина. Пример: если настроить пин на максимальную частоту 2 МГц, но при этом подать 72 МГц, то сигнал на этом пине будет больше похож на синус, чем на меандр. Если же настроить на 50 МГц, то в этом случае на выходе будет уже что-то более-менее похожее на меандр

Где-то обсуждали (ссылаясь на доки), что частота переключения пинов в STMках отражает на самом деле скорость нарастания/спада фронта, а значение частоты — это максимальная частота, при которой сумма времени нарастания и спада не более двух третей от стабильного уровня импульса. Быстрое переключение выводов может оказывать электромагнитное влияние на работу самого микроконтроллера. Поэтому, если нет необходимости в высоких частотах, предлагается устанавливать опцию более низкой частоты (т.е. более медленные изменения уровня сиглала на ножках) в целях более высокой помехоустойчивости. При этом, если повышенная частота нужна, то такая опция такая есть.

Примеры на Си для микроконтроллеров Atmel AVR

Здесь представлены примеры различных программ на языке Си для микроконтроллеров Atmel AVR. Все примеры написаны под микроконтроллер ATmega16, поэтому при переносе на другие МК семейства AVR это нужно учитывать. Тактовая частота микроконтроллера во всех примерах 8 МГц (используется тактирование от внутреннего генератора). Код примеров разбит на блоки и снабжен комментариями. Проекты написаны в среде Eclipse (инструкция по установке и настройке Eclipse для AVR) и легко могут быть импортированы в Eclipse. Также можно использовать данные проекты и в среде AVR studio (изменится только структура файлов проекта). При обнаружении ошибок просьба сообщить на почту.

Blink – Самый простой пример. К порту C подключены 8 светодиодов. Светодиоды зажигаются логической единицей на линии порта. В цикле светодиоды порта включаются и выключаются. Свеобразный аналог Hello World в мире встраиваемых систем.

IO Ports – В данном примере рассматривается работа с портами ввода-вывода. К порту C подключены 8 светодиодов (линии 0-7). К линии 2 порта D подключена кнопка, с подтяжкой на землю. При нажатии кнопка выдает на линию 0 порта С уровень логической единицы. Цикл программы организован следующим образом: при запуске включается бегущий огонь, сначала загорается светодиод на линии 0 порта C, затем на линии 1 и т.д. По достижении линии 7 направление бегущего огня меняется (от 7 к 0). При нажатии на кнопку бегущий огонь останавливается и загораются одновременно все светодиоды. После повторного нажатия на кнопку бегущий огонь продолжает перемещаться с места остановки.

Dynamic Indication – В данном примере рассматривается работа с 7-сегментным индикатором. В моём случае он имеет 4 разряда (цифры). Поскольку у меня на плате установлены транзисторы для управления разрядами, то управление осуществляется выводом логической единицы и на разряды и на сегменты. Схема подключения следующая: к линиям 0-7 порта C подключены сегменты индикатора, а к линиям 0-3 порта В разряды индикатора. При запуске на индикатор выводятся цифры 1 2 3 4.

UART – В данном примере рассматривается периферийного модуля UART (универсальный асинхронный приёмопередатчик). Модуль UART можно настроить как на работу с прерываниями, так и без них (вручную, путём работы с флагами). Пример работает следующим образом: при получении байта, МК переходит в обработчик прерывания (используется только прерывание по приёму данных) и разбирает численное значение байта (0-255) на цифры, которые и выводятся на 7-сегментный индикатор. Схема подключения аналогична предыдущему примеру. Передача осуществляется по двум линиям UART (порт D линии 0-1), к которым необходимо подключить линии RX и TX преобразователя USB-UART. Для настройкки без прерываний необходимо обнулить бит RXCIE в регистре UCSRB и вручную опрашивать интерфейс в основном цикле программы.

Clock – В данном примере рассматривается реализация простых часов с 7-сегментным индикатором и парой кнопок. Только здесь уже требуется 6 разрядов, хотя секунды можно опустить. Кнопки с подтяжкой на землю. При нажатии кнопка выдает на линию высокий логический уровень. Индикатор подключается как и в предыдущих примерах (сегменты к порту C, разряды к порту B), а кнопки к линиям 2-3 порта D. Кнопка используется PD2 для установки минут, а PD3 для установки часов. По нажатию каждой из кнопок увеличивается значение соответствующего разряда (минуты или часы).

DS18B20 – В данном примере рассматривается работа с цифровым датчиком температуры DS18B20. Показания температуры выводятся на 7-сегментный индикатор. Вывод DQ датчика поключен к ноге (пину) PD5. Линия должна быть подтянута к плюсу питания резистором на 4.7-10 кОм (согласно документации). Датчик опрашивается каждые 5 секунд. Температура выводится на 4-разрядный индикатор: знак, два разряда на целуюю часть и один на вещественную. Документация к датчику здесь.

DHT11 – В данном примере рассматривается работа с датчиком температуры и влажности DHT11. Показания температуры выводятся на 7-сегментный индикатор. Вывод DATA (также SDA) датчика поключен к ноге (пину) PD5. Линия должна быть подтянута к плюсу питания резистором на 4.7-10 кОм (согласно документации). Датчик опрашивается каждые 5 секунд. Измеряются температура и влажность, но на дисплей выводится только влажность (целое двухзначное число). Документация к датчику здесь.

DHT22 – В данном примере рассматривается работа с датчиком температуры и влажности DHT22. По сравнению с DHT11 данный датчик обладает большей точностью и более широким диапазоном измерений. Показания температуры выводятся на 7-сегментный индикатор. Вывод DATA (также SDA) датчика поключен к ноге (пину) PD5. Линия должна быть подтянута к плюсу питания резистором на 4.7-10 кОм (хотя согласно документации это и необязательно). Датчик опрашивается каждые 5 секунд. Измеряются температура и влажность, но на дисплей выводится только влажность (вещественное двухзначное число с одним знаком после запятой). Документация к датчику здесь.

BMP180 – В данном примере рассматривается работа с цифровым датчиком температуры и атмосферного давления BMP180. Показания атмосферного давления выводятся на 7-сегментный индикатор. Датчик подключаетсяпо интерфейсу I2C. Линии SDA и SCL должны быть подтянуты к плюсу питания резисторами на 4.7-10 кОм. Датчик опрашивается каждые 10 секунд. Измеряются температура и давление, но на дисплей выводится только атмосферное давление в мм. ртутного столба (целое число). Документация к датчику здесь.

BH1750 – В данном примере рассматривается работа с цифровым датчиком освещенности BH1750. Показания освещенности выводятся на 7-сегментный индикатор. Датчик подключается по интерфейсу I2C. Линии SDA и SCL должны быть подтянуты к плюсу питания резисторами на 4.7-10 кОм. Датчик опрашивается каждые 5 секунд. Документация к датчику здесь.

ADC Indication – Данный пример аналогичен примеру с UART. Отличие в том, что байт берется с линии 0 порта А (линия 0 АЦП, ADC0). Микроконтроллер по таймеру производит аналого-цифровое преобразование напряжения на линии 0 порта А, (младшие 2 бита отбрасываются как шум). При измерении используется внутренняя опора 5 В. К линии PD2 порта D подключена кнопка, которая определяет режим вывода показаний. При нажатии на кнопку выводится результат измерений в виде числа от 0 до 255. Если кнопка не нажата, то результат измерений переводится в вольты и выводится на индикатор (с точностью до десятых).

Fast PWM – В данном примере показана настройка аппаратного ШИМ (широтно-импульсная модуляция, англ. PWM). К линиям 4 и 5 порта D подключены светодиоды, а к линиям 2-3 и 6-7 порта D – кнопки каналов A и B соответственно. Кнопки с подтяжкой на землю (при нажатии кнопка выдает на линию порта уровень логической единицы) Кнопки на линях 2 и 3 соответственно увеличивают и уменьшают коэффициент заполнения ШИМ (меняется яркость светодиода) канала А. Кнопки на линях 6 и 7 соответственно увеличивают и уменьшают коэффициент заполнения ШИМ канала B. Число сравнения для каждого из каналов меняется в диапазоне от 0 до 255. Для канала А шаг изменения равен 10, для канала В шаг равен 5.

HCSR04 – В данном примере рассматривается работа с ультразвуковым датчиком расстояния HCSR04. К линии PD3 подключен вывод Trigger датчика, а к линии PD2 вывод Echo. Поключение 7-сегментного индикатора аналогично предыдущим примерам. МК периодически опрашивает датчик и определяет расстояние до препятсвия в сантиметрах. После этого число разбивается на цифры и выводится на дисплей. Документация к датчику здесь.

Matrix Keyboard – В данном примере показана работа с матричной клавиатурой. Микроконтроллер динамически опрашивает клавиатуру, а затем определяет номер нажатой клавиатуры. Размер поля 3 на 3 – получаем 9 кнопок. Нажатие первых 8-ми приводит к зажиганию светодиода на соответствующей линии порта А, нажатие 9-ой кнопки зажигает все светодиоды порта А. Матричная клавиатура подключается к линиям 0-5 порта С (три столбца и три строки). В архиве схема и печатная плата матричной клавиатуры (Diptrace).

Shift Register – В данном примере рассматривается работа с модулем SPI на примере сдвигового регистра 74HC595. К регистру подключены светодиоды, в качестве линии CS используется линия 4 порта B (вывод not SS). Линия DS (14 нога) регистра идет к MOSI (PB5), линия SHCP (11 нога) к линии SCK (PB7), линия STCP (12 нога) к линии SS (PB4). Линии MR (10 нога) и OE (13 нога) должны быть подтянуты к высокому и низкому логическим уровням соответственно. По таймеру микроконтроллер меняет состояние светодиодов: поочерёдно горят то чётные светодиоды, то нечётные. Если при этом передать байт по UART’у, то он будет выведен в порт на светодиоды. Чтобы обратно переключиться в режим мигания необходимо послать по UART’у 0x00 (ноль). Документация к микросхеме 74HC595 здесь.

SG-90 Servo – В данном примере рассматривается работа с сервоприводом SG-90. Используется аппаратный ШИМ. Линия ШИМ сервпопривода подключена к каналу А аппаратного ШИМ. Кнопки поворота подключены к линиям PD2 и PD3. Кнопка на линии PD2 увеличивает длительность импульса, кнопка на линии PD3 уменьшает длительность импульса. Длительность импульса меняется от 1 до 2 мс. Описание сервомотора здесь.

RGB Lamp – В данном примере рассматривается работа с трехцветным RGB-светодиодом. Реализовано плавное переливание цветов с использованием программного ШИМ. Линии красного, зеленого и синего цветов подключаются соответственно к линиям 2, 3 и 4 порта D.

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

WS2812 Ring – В данном примере рассматривается работа со светодиодами WS2812 с встроенным ШИМ-контроллером. К контроллеру подключено такое кольцо из 16 светодиодов (количество светодиодов в кольце можно указать в коде). Библиотека для работы с WS2812 не моя (взята на гитхабе и немного допилена, копирайт сохранён). В программе сначала задается массив цветов (красный, зеленый, синий), а затем в цикле реализуется их сдвиг и плавным изменением интенсивности. Линия IN первого светодиода подключается к линии PD2 порта D. Описание светодиодов здесь.

MFRC522 RFID – В данном примере рассматривается работа со считывателем RFID карточек MFRC522. Cчитыватель подключён к контроллеру по стандартной схеме. Библиотека для работы с MFRC522 не моя (взята на гитхабе и немного допилена, копирайт сохранён). При запуске контроллер определяет тип ридера и отправляет данные в UART. Затем идет непрерывная проверка обнаружения RFID устройств. При поднесении карточки или брелка считывается его адрес и отправляется в UART (адрес 32 бита, 4 байта). Описание считывателя здесь.

Структуры, перечисления

Немного очевидных вещей, которые не так часто используются в AVR микроконтроллерах, зато достаточно популярны при написании кода для STM32. В частности для настройки периферии посредством библиотеки standart peripheral library (SPL)

Рассмотрим следующий кусок кода:

typedef enumspeed; speed SPEED; SPEED = low; printf(«%dn», SPEED);

Пофантазируем. Представьте себе ситуацию: вы сделали некое устройство, пускай это будет абстрактный датчик температуры, который передает данные по uart. Теперь задача написать библиотеку, которая бы предоставляла дальнейшим пользователям простой интерфейс, т.е. ее можно было бы прицепить к любому проекту. Допустим датчик может корректно работать только на трех скоростях uart 9600, 19200 и 57600.

Можно пытаться объяснить этот момент в документации, но кто будет ее читать? Поэтому гораздо правильнее написать такой код, чтобы пользователь не мог ошибиться. В этом очень здорово могут помочь перечисления. Вернемся к нашему куску кода. Есть некое перечисление, о чем нам говорит ключевое слово enum, название которого speed. Назвать перечисление мы вольны как угодно.
typedef enumspeed;

Далее по коду мы выделяем память
speed SPEED;

Теперь у нас есть переменная SPEED, которая может принимать только 3 значения low, mid, high. Соответственно когда мы присваиваем SPEED = low; то переменная SPEED становится равной 9600. Если мы попытаемся записать в SPEED все что угодно, кроме low, mid, high, то проект не скомпилится и вылезет ошибка. Таким образом, у того кто будет использовать вашу библиотеку шанс ошибиться намного меньше.

Пример из реальной жизни. Все кто использовал SPL для создания проектов для STM32 совершенно точно видел подобные куски кода

GPIO_InitTypeDef PA10_setup; PA10_setup.GPIO_Mode = GPIO_Mode_Out_PP; PA10_setup.GPIO_Pin = GPIO_Pin_10; PA10_setup.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOA, &PA10_setup);

Выколите мне глаза, кричали они Но на деле, если взглянуть в инклюды, то все просто например PA10_setup.GPIO_Speed это всего навсего перечисление, выглядит оно так:

Периодически народ спрашивает, откуда ты берешь все эти значения для инициализации? Да вот же они! Все возможные варианты. Достаточно понимать что ищешь. Тоже самое с остальными настройками.

Теперь перейдем к структурам. Знаете ли вы что такое массив? Это объединение нескольких одинаковых переменных под одним именем. Например, вы измерили 3 раза температуру за окном. Это удобно и понятно, все 3 температуры относятся к некому одному объекту и имеют одинаковый тип данных char.
char temp[3];
temp[0] = 25, temp[1] = 15, temp[2] = 10.

Но иногда может быть так, что нужно привязать к одному объекту, переменные разного типа данных char, int и т.п. Типичный пример, у человека есть рост, вес и цвет волос. Рост и вес типа char, а цвет волос кодируется 3 байтами (RGB). В этом случае удобно использовать структуры.

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

#include «stdafx.h» int _tmain(int argc, _TCHAR* argv[]) < struct DOG //структура DOG, с помощью которой можно составить описание собаки < char gender; //возраст int weight; //вес char *color; //цвет (указатель) >; struct DOG sharik; //выделяем память под шарика struct DOG bobik; //выделяем память под бобика sharik.gender = 2; //возраст шарика 2 года sharik.weight = 17; //вес 17 кг sharik.color = «black»; //цвет черный bobik.gender = 5; //возраст шарика 5 лет bobik.weight = 15; //вес 15 кг bobik.color = «white»; //цвет белый printf(«Sharik:n weight: %d, gender: %d, color: %snr», sharik.weight, sharik.gender, sharik.color); printf(«==============================================nr»); printf(«Bobik:n weight: %d, gender: %d, color: %snr», bobik.weight, bobik.gender, bobik.color); printf(«==============================================nr»); return 0; >

Результат

Программа полна коментов, но все же поясним. Вначале создается структура с именем DOG, т.е. собака. Все собаки имеют общие признаки возраст, вес и цвет. Соответственно 3 переменных. Чтобы можно было отличить по программе шарика от бобика, создается переменная типа структуры, например sharik, т.е. выделяется память под переменные возраста, цвета и веса. Доступ к переменным осуществляется через точку.

Очевидно, что если делать подобную программу без структуры, она скорее всего содержала бы кучу переменных типа char sharik_gender, sharik_weight. Поэтому программа со структурами выглядит намного нагляднее и понятнее. Кроме того, существует несколько хитростей, благодаря которым можно сильно упростить разбор протоколов, но об этом в другой раз, когда сам хорошо овладею 🙂

Ну и еще один момент, про который промолчали выше. Для тех кого напрягает каждый раз писать struct DOG sharik, можно сделать синоним, для этого используется ключевое слово typedef

typedef struct //создаем синоним структуры под именем DOG < char gender; //возраст int weight; //вес char *color; //цвет >DOG; //теперь можно обращаться без ключевого имени struct DOG sharik; //выделяем память под шарика DOG bobik; //выделяем память под бобика

Собственно вернемся к изначальной цели, откуда мне взять все эти настройки для периферии STM. Возьмем наш пример и GPIO, заходим в stm32f10x_gpio.h и находим ту самую структуру.