Дек 112011
 

Впервые взглянув на количество регистров порта ввода/вывода общего назначения — GPIO, кажется, что разобраться с работой порта не так уж и просто. Но на самом деле, достаточно единожды прочитать о назначении каждого из регистров, чтобы всю эту информацию запомнить. Названия регистров – это аббревиатуры, в которых отображено, для какой цели данный регистр предназначен.

Далее список регистров для любого из GPIO и их предназначение.

GPIOx_MODER (port mode register). Судя по названию, в регистре задаются режимы работы индивидуально для каждой из линий порта. Каждый из выводов GPIO может быть настроен как вход, выход, работать в аналоговом режиме, или подключен к одной из альтернативных функций. Регистр 32-разрядный, за настройку каждого из выводов соответствующего порта отвечают 2 бита данного регистра.

GPIOx_OTYPER (port output type register). Когда вывод порта в режиме выхода или альтернативной функции, соответствующий бит регистра устанавливает тип выхода. Это может быть Push-Pull (двухтактный) или Open Drain (выход с открытым стоком).

GPIOx_OSPEEDR (port output speed register). Задает скорость работы порта, каждому выводу соответствуют два разряда регистра. Возможны следующие варианты: 400кГц, 2МГц, 10МГц и 40Мгц.

GPIOx_PUPDR (port pull-up/pull-down register). Задает подключение подтягивающих резисторов, каждому выводу соответствуют два разряда регистра. Существуют следующие варианты, без подтягивающего резистора, с подтяжкой к «+» питания, с подтяжкой к «gnd». Комбинация бит «11» зарезервирована.

GPIOx_IDR (input data register). Тут все просто, это регистр входных данных, из которого считывается состояние входов порта.

GPIOx_ODR (output data register). Соответственно, наоборот, регистр выходных данных. Запись числа в младшие 16 бит, приводит к появлению соответствующих уровней на выводах порта.

GPIOx_BSRR (port bit set/reset register). Это регистр побитовой установки/сброса данных на выходных линиях порта. 32 разряда этого регистра позволяют индивидуально установить или сбросить каждый из 16 младших разрядов регистра GPIOx_ODR. Младшие 16 разрядов регистра GPIOx_BSRR отвечают за установку соответствующего бита регистра GPIOx_ODR в «1», старшие 16 разрядов сбрасывают этот бит. Установка/сброс осуществляются записью «1» в соответствующий разряд. Запись «0» никак не воздействует на состояние соответствующего бита выходного регистра данных. При одновременной записи двух единиц в биты установки и сброса, приоритет имеет операция установки бита. Этот регистр дает возможность выполнения «атомарных» операций побитового управления выходными линиями порта. При этом нет риска возникновения прерывания между операциями чтения и модификации при записи числа в выходной регистр GPIOx_ODR. Атомарные операции с регистром GPIOx_BSRR выполняются за один цикл записи. При этом операции установки/сброса имеют однократный эффект. Предыдущее состояние модифицируемого бита регистра GPIOx_BSRR совершенно неважно, можно сколько угодно «пихать» туда единицы и каждый раз регистр GPIOx_ODR будет реагировать соответствующим образом.

GPIOxLCKR (port configuration lock register). Позволяет «заморозить», то есть защитить от изменения текущую настройку конфигурации. Можно запретить модификацию следующих регистров управления:

GPIOx_MODER,
GPIOx_OTYPER,
GPIOx_OSPEEDR,
GPIOx_PUPDR,
GPIOx_AFRL,
GPIOx_AFRH.

GPIOx_AFRL, GPIOxAFRH (alternate function low/high register). Эти 2 регистра предоставляют возможность выбора одной из 16 альтернативных функций для каждой линии ввода/вывода. Это подразумевает собой подключение линии порта к одному из периферийных устройств микроконтроллера и дальнейшую работу под его управлением. Такой режим мультиплексирования позволяет более гибко назначать входы/выходы периферийных устройств относительно физических выводов кристалла. Ниже приведена таблица с различными режимами настройки портов, взятая из Reference manual.

Port bit config

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

#include "stm32l1xx.h"
#include "stm32l1xx_gpio.h"

#define LD_PORT GPIOB
#define LD_GREEN GPIO_Pin_7
#define LD_BLUE GPIO_Pin_6

#define GPIO_HIGH(a, b) a -> BSRRL = b
#define GPIO_LOW(a, b) a -> BSRRH = b

void InitPeriph(void);
void Delay(uint32_t step);

int main()
 {
  InitPeriph();
  while(1)
  {
   GPIO_LOW(LD_PORT, LD_BLUE);
   GPIO_HIGH(LD_PORT, LD_GREEN);
   Delay(0x0001FFFF);
   GPIO_LOW(LD_PORT, LD_GREEN);
   GPIO_HIGH(LD_PORT, LD_BLUE);
   Delay(0x0001FFFF);
  }
 }

void InitPeriph(void)
 {
  RCC->AHBENR |= RCC_AHBENR_GPIOBEN;
  GPIOB->MODER |= GPIO_MODER_MODER7_0;
  GPIOB->OTYPER &= ~GPIO_OTYPER_OT_7;
  GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR7;
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;

  GPIOB->MODER |= GPIO_MODER_MODER6_0;
  GPIOB->OTYPER &= ~GPIO_OTYPER_OT_6;
  GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR6;
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;
 }

void Delay(uint32_t step)
 {
  while(step>>0)
  {
   --step;
  }
 }

В самом начале директивами #include подключаются два файла:

stm32l1xx.h

и

stm32l1xx_gpio.h.

В начале файла stm32l1xx.h описаны все регистры периферии и заданы адреса для доступа к ним. Затем идут отдельные секции для каждого периферийного устройства с макроподстановками #define, в которых для удобства программиста числовые битовые маски заменены на удобные для понимания идентификаторы. Это значительно облегчает написание кода, теперь для настройки периферии не нужно каждый раз разбираться с битами конкретного регистра. Достаточно заглянуть в файл stm32l1xx.h , найти там соответствующую секцию и нужный идентификатор, тем более что все они снабжены комментариями. Например, чтобы настроить в режим выхода линию 0 порта, находим секцию «Bit definition for GPIO_MODER register» и в ней видим следующие макроподстановки для линии 0 порта:

#define GPIO_MODER_MODER0 ((uint32_t)0x00000003) 
#define GPIO_MODER_MODER0_0 ((uint32_t)0x00000001) 
#define GPIO_MODER_MODER0_1 ((uint32_t)0x00000002)


Теперь, операция настройки вывода 0 порта в режим «выход» будет иметь такой вид:

GPIOx->MODER |= GPIO_MODER_MODER0_0; 


Из файла «stm32l1xx_gpio.h» нам понадобятся макроподстановки для отдельных бит порта вида

#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */ 
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */ 


так как далее в нашей программе делаются свои макроподстановки для задания выводам портов, к которым подключены светодиоды, понятных имен.

#define LD_PORT GPIOB 
#define LD_GREEN GPIO_Pin_7 
#define LD_BLUE GPIO_Pin_6 


Следующие два #define – это макроопределения

#define GPIO_HIGH(a, b) a -> BSRRL = b 
#define GPIO_LOW(a, b) a -> BSRRH = b 

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

Далее следуют объявления функций инициализации портов и формирования временной задержки

void InitPeriph(void); 
void Delay(uint32_t step);


Итак, мы подошли к основной функции main(), с которой начинается выполнение программы. Вначале следует вызов функции инициализации периферии (в нашем случае порта B)

InitPeriph();

Перейдем в эту функцию и посмотрим, что в ней настраивается.

Первая операция здесь

RCC->AHBENR |= RCC_AHBENR_GPIOBEN; // разрешение тактирования порта B 

Для того, чтобы порт работал, на него надо подать тактовую частоту. Для этого есть три шины: AHB, APB1, APB2. В разных семействах STM32 тактирование может осуществляться от различных шин. Для уточнения можно снова заглянуть в файл stm32l1xx.h. В нашем случае для тактирования портов задействована шина AHB.

Далее настраиваем соответствующую линию порта B режим "Выход"

GPIOB->MODER |= GPIO_MODER_MODER7_0; //Настройка линии 7 порта B в режим выхода 


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

GPIOB->OTYPER &= ~GPIO_OTYPER_OT_7; 
GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR7; 

Эти операции вставлены в программу для более полного описания порядка настройки GPIO. Первая операция устанавливает режим Push-Pull (двухтактный выход) для 7 линии порта B (напомню, что второй возможный режим – это выход с открытым стоком). Вторая операция отвечает за подключение подтягивающих резисторов на линии порта (Pull-Up или Pull-Down), в нашем случае резисторы отключены.

Следующая операция

GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7; 

задает скорость работы линии порта. Возможны следующие варианты:

  • 00: 400 kHz Very low speed
  • 01: 2 MHz Low speed
  • 10: 10 MHz Medium speed
  • 11: 40 MHz High speed on 50 pF (50 MHz output max speed on 30 pF)

В нашем случае установлена максимальная частота 40 МГц.

Далее полностью аналогичные операции производятся для линии 6 порта B.

GPIOB->MODER |= GPIO_MODER_MODER6_0; 
GPIOB->OTYPER &= ~GPIO_OTYPER_OT_6; 
GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR6; 
GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;


После завершения операций инициализации нужных линий порта снова возвращаемся в главную функцию main(). И далее попадаем в бесконечный цикл

while(1)
 {
  GPIO_LOW(LD_PORT, LD_BLUE): //Гасим голубой светодиод
  GPIO_HIGH(LD_PORT, LD_GREEN); //Зажигаем зеленый светодиод
  Delay(0x0001FFFF); //Временная задержка
  GPIO_LOW(LD_PORT, LD_GREEN); //Гасим зеленый светодиод
  GPIO_HIGH(LD_PORT, LD_BLUE); //Зажигаем голубой светодиод
  Delay(0x0001FFFF); //Временная задержка
 }

Операции GPIO_HIGH() и GPIO_LOW(), а это у нас макрофункции, определенные выше с помощью директив препроцессора #define, выполняют установку соответствующих бит в регистре BSRR, что приводит к установке/сбросу отдельных бит в регистре выходных данных ODR, и соответственно управляет уровнями напряжения на линиях 6, 7 порта B. А там у нас подключены светодиоды.

После операций включения/выключения светодиодов каждый раз вызывается функция формирования временной задержки

Delay(0x0001FFFF);


При этом попадаем в тело функции, расположенной в нижней части программы

void Delay(uint32_t step)
 {
  while(step>>0)
  {
    --step;
  }
 } 

Как видим в функцию передается параметр step, равный 0x0001FFFF. Значение выбрано произвольно.

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

Project -> Rebuild All

Project -> Download and Debug (CTRL+D или нажав Download and Debug на панели инструментов).

Затем на панели Debug

Toolbars_Debug

запустить выполнение программы, нажав кнопку Go Toolbars_Debug_Go.

Для выполнения программы в симуляторе, необходимо снова вызвать меню настройки опций проекта Project -> Options и в категории Debugger на вкладке Setup в выпадающем списке Driver установить параметр Simulator. Теперь можно снова запустить отладчик из меню Project -> Download and Debug (CTRL+D или нажав Download and Debug на панели инструментов). Далее выполняется отладка в симуляторе. Можно отобразить содержимое различных регистров микроконтроллера через меню View -> Register. Открыв список регистров, выберите CPU Registers. В самом конце списка расположены регистры CYCLECOUNTER, CCTIMER1, CCTIMER2, CCSTEP. С помощью этих регистров можно измерять количество тактов микроконтроллера, затраченное на выполнение какого-либо фрагмента кода. То есть можно проверять длительность временных задержек и т.п.Впервые взглянув на количество регистров порта ввода/вывода общего назначения — GPIO, кажется, что разобраться с работой порта не так уж и просто. Но на самом деле, достаточно единожды прочитать о назначении каждого из регистров, чтобы всю эту информацию запомнить. Названия регистров – это аббревиатуры, в которых отображено, для какой цели данный регистр предназначен.

По ссылке ниже можно скачать архив с проектом и библиотеками

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

Другие статьи:

  25 Responses to “STM32. Создание проекта в IAR EWARM. Работа с портами ввода/вывода. Часть 2.”

  1. Можно ли выложить готовый рабочий проект?
    Или подскажите как победить ошибку Error[Li005]: no definition for «TimingDelay_Decrement».

  2. А почему скорость работы порта выбрана равной 40 мгц? Не совсем понятно для чего этот параметр.

    • Это максимально возможная частота выходного сигнала. В даташите табличка есть, там прописаны максимальные частоты при различных значениях в регистре OSPEEDR, а также время нарастания и спада сигнала. Измерено при емкости нагрузки 50 пФ. Фактически, значением в регистре задается скорость заряда/разряда емкости нагрузки до нужного логического уровня. Фронты короче — максимально допустимая частота выше соотвественно. К примеру, время нарастания 625 нс — макс. частота вывода в порт 400 кГц (OSPEEDR = 00), если же время нарастания сигнала 5 нс — макс. скорость уже 50 МГц (OSPEEDR = 11).
      Также написано, что выход порта напрямую соединяется с соответствующим разрядом регистра OSPEEDR. Как там физически все реализовано информации не нашел, остается догадываться что значениями в регистре фактически задаются различные токи заряда нагрузочной емкости, соответственно и время нарастания сигнала разное и частота допустимая. Можно предположить, что различные скорости вывода опять же завязаны с энергопотреблением, если скорости ограничить, соответственно токи заряда емкостей меньше будут.

    • Для этого примера скорость не важна, можно минимальную задать. 40 МГц — случайный выбор.

  3. а если писать без define, то устанавливать и сбрасывать биты
    GPIOB->BSRRL=GPIO_BSRR_BS_6;
    GPIOB->BSRRL=GPIO_BSRR_BR_6;
    верно ?

    • Да. Только так
      GPIOB->BSRRL = GPIO_BSRR_BS_6;
      GPIOB->BSRRH = GPIO_BSRR_BS_6;
      или так
      GPIOB->BSRRL |= GPIO_BSRR_BS_6;
      GPIOB->BSRRH |= GPIO_BSRR_BS_6;
      Чтобы сбросить, надо тоже бит установить в BSRRH
      Запись
      GPIOB->BSRRL=GPIO_BSRR_BR_6 не работает, регистр тут на два 16-ти разрядных разбили.

  4. Delay(0x0001FFFF); //Временная задержка- фактический это задержка на количество тактов ? ! Если предположить что тактовая частота 8МГц, то почему (0x0001FFFF = 131071 такт = 0.016 сек) числа хватает что бы увидеть как нормально, в полную силу моргает светодиод ? Разве не нужно выставить большее число !? Просто что считает контроллер ? ( именно в этом примере).

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

    • 0x0001FFFF = 131071 такт = 0.016 сек
      Неверно, это же не таймер. Каждая операция (декремент, проверка условия, переходы) может длиться несколько тактов. К тому же частота по умолчанию 2,097 МГц.

  5. PANYTA

    «»Теперь, операция настройки вывода 0 порта в режим «выход» будет иметь такой вид:

    GPIOx->MODER |= GPIO_MODER_MODER0_0; »

    А как будет выглядеть настойка порта на вход ?

  6. Выше в тексте есть таблица 18, взятая из Reference manual. Соответственно, для установки режима входа необходимо сбросить биты MODER:
    GPIOx->MODER &= ~GPIO_MODER_MODER0 — так, например, для разряда 0 порта STM32L1xx. Маски регистров наподобие GPIO_MODER_MODER0 прописаны в файлах stm32l1xx.h, stm32f10x.h и т.п.
    Для серий STM32F10x регистры настройки портов отличаются, в разделе GPIO Reference manual есть подобная таблица с режимами настройки портов.

  7. PANYTA

    «сбросить биты MODER» — спасибо, понял. (мои вопросы не столько по STM или IAR, сколько по C — всю жизнь на ассемблере…)

    Подскажите, пожалуйста ещё.
    пробую обрабатывать кнопку:

    while(1)
    {
    if ((GPIOA->IDR & GPIO_OTYPER_IDR_0)==0x00) //Кнопка нажата?
    {
    GPIO_LOW(LD_PORT, LD_BLUE);
    GPIO_HIGH(LD_PORT, LD_GREEN);
    Delay(0x0001FFFF);
    GPIO_LOW(LD_PORT, LD_GREEN);
    GPIO_HIGH(LD_PORT, LD_BLUE);
    Delay(0x0001FFFF);
    }
    else
    {
    GPIO_HIGH(LD_PORT, LD_BLUE);
    GPIO_HIGH(LD_PORT, LD_GREEN);
    Delay(0x0001FFFF);
    GPIO_LOW(LD_PORT, LD_GREEN);
    GPIO_LOW(LD_PORT, LD_BLUE);
    Delay(0x0001FFFF);
    }
    }

    Порт инициализировал так:

    GPIOA->MODER &= ~GPIO_MODER_MODER0; //input
    GPIOA->OTYPER &= ~GPIO_OTYPER_OT_0; //Output push-pull
    GPIOA->OSPEEDR |=GPIO_OSPEEDER_OSPEEDR0; //40 MHz
    GPIOA->PUPDR &=~GPIO_PUPDR_PUPDR0; //No pull-up, pull-down

    Не реагирует на нажатие. Что не так ?

  8. PANYTA

    Отвечаю сам себе :)
    Добавил строку:
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN; // разрешение тактирования порта A

    Но остался вопрос, почему без этой строки в симуляторе всё работало ?

  9. PANYTA

    «Варианты возможного распределения альтернативных функций по выводам приведены в datasheet на микроконтроллер, таблица с возможными вариантами там занимает целых 5 страниц. Далее разберемся с текстом программы.»

    Где найти эти пять страниц ? В Reference manual ? Или в STM32L15xx6/8/B ?

  10. В последнем.

  11. SPinFly

    Где посмотреть какой режим порта выставлять на работу с периферией? Например в каком режиме должен быть порт для RX или TX модуля USART?

    • User manual на семейство в основном содержит эти сведения. А также в стандартных библиотеках есть примеры, можно там подсмотреть.

      • SPinFly

        Прошерстил весь Refference Manual и Datasheet на stm32L152 — результат ноль. В серии stm32f103 информация про настройки портов для альтернатив ф-ии (pull up, pull down, PushP, OpenD) находится в виде таблиц в RM в графе порты GPIO что очень удобно. Ну а в серии stm32L152 и stm32f4xx такой информации нет. Буду очень благодарен если ткнете носом туда где подобная информация есть.

        • Настройка портов для UART у меня есть в статьях, можете там посмотреть. Для серии STM32L альтернативные функции прописываются несколько по другому, есть специальные регистры. Причем информация разбросана по user manual и datasheet. Я вот конкретно сейчас не могу ткнуть носом в страницу, просто не могу помнить, там много информации, надо искать.

  12. Здравствуйте, пытаюсь в IAR выдать 1 на 12 ногу F407///

    int main()
    { //SystemInit();

    //RCC_AHB1PeriphClockCmd(RCC_Ahb1Periph_GPIOD, Enable);
    //RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
    GPIOD->MODER |= GPIO_MODER_MODER12_0; //output
    GPIOD->OTYPER &= ~GPIO_OTYPER_OT_12; //Output push-pull
    GPIOD->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR12_1; //10 MHz
    GPIOD->PUPDR &=~ GPIO_PUPDR_PUPDR12; //No pull-up, pull-down
    //GPIOD->ODR=0xFFFF;
    //GPIOD->BSRRL = GPIO_BSRR_BS_12;

    В коментариях варианты компиляции, никаких ошибок все хорошо, заливаю hex и ничего не происходит