IAR и STM32 CORTEX M0. Часть 0x05, GPIO — входит и выходит…

Чтобы не скатиться в вольный перевод даташита и прочих мануалов, предлагаю сразу начать разбор кода предыдущей статьи и по ходу дела обращаться к докам. Начинается код весьма незамысловато: подключаем заголовочный файл, объявляем самописную функцию delay и главную функцию main:

 #include "stm32f0xx.h"  void delay (int a);  void main(void) {

Диодиками будем мигать при помощи портов PA3 и PA4. По умолчанию все порты (как и большая часть периферии) выключены — в данном случае нам нужно подключить GPIOA (был бы порт PB, подключали бы GPIOB и т.д.). Как это сделать? Для начала неплохо бы узнать, а от чего этот интерфейс получает тактование. Для этого в даташите есть две схемы: блок-схема на 11-й странице (Block diagram) и схема тактования на 15-й странице (Clock tree). Пока нас интересует только часть с GPIO:

gpio2-1

Видно, что интерфейс GPIOA идет на шину AHB — теперь есть, от чего отталкиваться. Забегая вперед (на несколько статей, когда будут разбираться LSE, HSE и т.д.), за тактование отвечает структура RCC (Reset and Clock Control — если интересно, стр.87 RM). Зная это, наберем:

 RCC->

Должен появиться выпадающий список, как на картинке. Если его не появилось, попробуйте перезапустить IAR и открыть проект заново (также проверьте, что у вас подключен заголовочный файл stm32f0xx.h). Не нужно быть семи пядей во лбу, чтобы понять, что для подключения к шине AHB нужно выбрать AHBENR . Дальше комбинация «или-равно» ( = ), а что писать после знака равенства? Памятуя о том, что «магические» числа — зло, будем использовать стандартные константы. Они объявлены в файле stm32f0xx.h. Он открывается прям из дерева двойным щелчком (выделено на картинке выше) и будет вашей главной шпаргалкой (ага, пока не дойдем до прерываний).

Как сориентироваться в stm32f0xx.h — там более 3k строк? Помогает, что содержимое файла хорошо прокомментировано. Выбрав нужный элемент, ищем его в stm32f0xx.h, добавив через пробел r (начало слова register). Так, для структуры с элементом RCC->AHBENR ищем «AHBENR r»:

Тут можно либо также действовать по логике (RCC_AHBENR_GPIOAEN намекает), либо обратиться к достаточно подробным комментариям (справа написано GPIOA clock enable — как раз то, что нам нужно). Теперь можно вернуться в main.c и записать строчку полностью:

 RCC->AHBENR = RCC_AHBENR_GPIOAEN

Следующий шаг — выбрать режим, в котором будут работать PA3 и PA4. Порты могут работать на вход (Input), на выход (Output) и в режиме альтернативной функции (AF). Поскольку мы разбираем мигалку, нам нужен режим Output, остальное же рассмотрим в следующих статьях.

Для того, чтобы выбрать режим, прежде всего нужно инициализировать MODER (в данном случае назначить 01). Настраивать будем GPIOA, потому в main.c пишем GPIOA->MODER (элемент MODER автоматически появится после стрелки). Дальше в заголовочном файле ищем «MODER r»:

MODER0 — это для нулевого порта, MODER1 — для первого и т.д. Но что означают цифры после подчеркивания? Если нет комментариев, следует обратиться к значениям нулевого элемента. Смотрим: MODER0 равно 0x3 или (в двоичной системе) 0b11. В свою очередь, MODER0_0 = 0x1 = 0b01 и MODER0_1 = 0x2 = 0b10. Нам нужно (см. таблицу выше) значение регистра MODER, равное 0b01, то есть MODERxx_0, где xx — номер порта. Таким образом, настройка MODER для PA3 и PA4 будет выглядеть так:

 GPIOA->MODER = (GPIO_MODER_MODER3_0  GPIO_MODER_MODER4_0);

Следующий столбец в таблице выше — OTYPER. Нам нужен режим push-pull (об этом чуть ниже), потому обнуляем. Точно по той же схеме, что и раньше, получаем:

 GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_3  GPIO_OTYPER_OT_4);

Самые внимательные спросят, зачем обнулять то, что по умолчанию и так ноль. Дело в том, что в мануале часто попадаются такие оговорки (пример для регистра GPIO_MODER):

Reset values:

0x2800 0000 for port A
 0x0000 0000 for other ports

То есть, после сброса мк, не на всех портах A (PA0-PA15) регистр GPIO_MODER равен «00». Переведем в двоичный вид:

0x2800 0000 = 00 10 10 00 00 00 00 00 00 00 00 00 00 00 00 00b

Если мы наложим результат на таблицу регистра GPIOx_MODER, представленную в RM, будет видно, что для портов PA13 и PA14 в регистре MODER[1:0] по умолчанию записано «10»:

Поэтому, имеет смысл обнулять регистры — меньше головной боли потом. Так и сделаем, добавим перед строкой инициализации MODER:

 GPIOA->MODER &= ~(GPIO_MODER_MODER3  GPIO_MODER_MODER4);

И да, на самом деле можно так не извращаться с переводом в двоичную систему и т.д. — в конце раздела с регистрами дается полная таблица (register map), где можно посмотреть значение всех регистров по умолчанию:

Впитали? Идем дальше. Регистр OSPEEDR устанавливает максимальную скорость для порта. Диодики у нас будут мигать не слишком быстро (уж точно реже 2 МГц), так что можно тут ничего не трогать.

Наконец, важный регистр PUPDR. Порт на выход может работать в 6 режимах. Это режимы push-pull и open-drain, каждый из которых можно настроить с подтяжкой на землю, на питание (pull-down и pull-up соответственно) или вовсе без подтяжки.

Режим push-pull должен быть вам знаком (если вы работали, например, с AVR). Логика простая: выставляем «1» — на порту напряжение питания, выставляем «0» — порт превращается в GND.

Режим open-drain интересней: если выставлен «0», то порт (как и в первом случае) выполняет роль «земли», а вот единичка переводит порт в Hi-Z (высокоимпедансный режим, когда сопротивление порта можно считать равным бесконечности).

pull-down и pull-up — соответственно подтяжка порта внутренними резисторами к питанию или земле, например, чтобы не ловили помех.

В нашем случае подтяжки не нужны (порты в воздухе не висят), потому выбираем обычный режим push-pull (00). Чтобы лишний раз не загромождать код, я не буду здесь приводить явного обнуления, а вы для тренировки можете это сделать.

Переходим непосредственно к миганию. Для это нам нужно: включить порт PA3, выключить PA4, потупить около секунды (паузу сделать), включить PA4, выключить PA3, снова потупить и все это взболтать зациклить. За включение/выключение портов отвечает регистр BSRR (Bit Set Reset Register). Пошерстив мануалы и заголовочный файл, несложно понять, что включить порт можно константой GPIO_BSRR_BS_xx, а выключить — GPIO_BSRR_BR_xx, где xx — номер порта. В результате получаем:

 GPIOA->BSRR = GPIO_BSRR_BS_3; GPIOA->BSRR = GPIO_BSRR_BR_4; delay(500000); GPIOA->BSRR = GPIO_BSRR_BR_3; GPIOA->BSRR = GPIO_BSRR_BS_4; delay(500000);

Что за функция delay()? Самописная функция, при значении 500000 тормозит около секунды:

 void delay (int a) {  int i,j;  for (i=0 ; i<a ; i++) {   j++;  } }

Значение 500000 подобрано опытным путем, позже раскурим таймеры (а потом и часовые кварцы), вот там и будем говорить о точности. А сейчас достаточно «примерно секунда». Вот мы и дописали программку мигания диодом:

 #include "stm32f0xx.h"  void delay (int a); void main(void) {  RCC->AHBENR = RCC_AHBENR_GPIOAEN;  GPIOA->MODER &= ~(GPIO_MODER_MODER3  GPIO_MODER_MODER4);  GPIOA->MODER = (GPIO_MODER_MODER3_0  GPIO_MODER_MODER4_0) ;  GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_3  GPIO_OTYPER_OT_4) ;  while (1) {   GPIOA->BSRR = GPIO_BSRR_BS_3;   GPIOA->BSRR = GPIO_BSRR_BR_4;   delay(500000);   GPIOA->BSRR = GPIO_BSRR_BR_3;   GPIOA->BSRR = GPIO_BSRR_BS_4;   delay(500000);  } }  void delay (int a) {  int i,j;  for (i=0; i < a; i++) {   j++;  } } 

На этом статью можно было бы закончить, однако осталось несколько подводных камней, так что… ныряем!

Настройка PF0 и PF1, как портов ввода-вывода
Выводы PF0 и PF1 предназначены для подключения кварца. Попытка использовать их, как стандартные порты ввода-вывода, может вызвать грусть, печаль и уныние. С первого взгляда вроде все просто, меняем GPIOA на GPIOF из прошлого примера и номера портов делаем соотвествующими:

 #include "stm32f0xx.h"  void delay (int a);  void main(void) {  RCC->AHBENR = RCC_AHBENR_GPIOFEN;  GPIOA->MODER &= ~(GPIO_MODER_MODER0  GPIO_MODER_MODER1);  GPIOF->MODER = (GPIO_MODER_MODER0_0  GPIO_MODER_MODER1_0) ;  GPIOF->OTYPER &= ~(GPIO_OTYPER_OT_0  GPIO_OTYPER_OT_1) ;  while (1) {   GPIOF->BSRR = GPIO_BSRR_BS_0;   GPIOF->BSRR = GPIO_BSRR_BR_1;   delay(500000);   GPIOF->BSRR = GPIO_BSRR_BS_1;   GPIOF->BSRR = GPIO_BSRR_BR_0;   delay(500000);  } }  void delay (int a) {  int i,j;  for (i=0; i < a; i++) {   j++;  } } 

Компилируем, заливаем и… ничего не происходит! Лампочки не мигают. Вот от слова совсем. И это может хорошо так попить вам крови. Обнуляем PUPDR — не сработало. Обнуляем MODER — нифига… Хорошо, будем плясать от печки. Выводы PF здесь предназначены для подключения внешнего кварца. Так… Возможно, при запуске нужно принудительно отключить внешний кварц? Попытка не пытка — добавляем строчку RCC->CR &= ~RCC_CR_HSEON:

 #include "stm32f0xx.h"  void delay (int a);  void main(void) {  RCC->AHBENR = RCC_AHBENR_GPIOFEN;  /* Пробуем отключить внешний кварц*/  RCC->CR &= ~RCC_CR_HSEON;  GPIOA->MODER &= ~(GPIO_MODER_MODER0  GPIO_MODER_MODER1);  GPIOF->MODER = (GPIO_MODER_MODER0_0  GPIO_MODER_MODER1_0) ;  GPIOF->OTYPER &= ~(GPIO_OTYPER_OT_0  GPIO_OTYPER_OT_1) ;  while (1) {   GPIOF->BSRR = GPIO_BSRR_BS_0;   GPIOF->BSRR = GPIO_BSRR_BR_1;   delay(500000);   GPIOF->BSRR = GPIO_BSRR_BS_1;   GPIOF->BSRR = GPIO_BSRR_BR_0;   delay(500000);  } }  void delay (int a) {  int i,j;  for (i=0; i < a; i++) {   j++;  } } 

Компилим, заливаем и… Черт побери, да оно работает? Но как так?! В таблице регистров четко указано, что при запуске мк регистр HSEON так же сброшен в ноль:

Тем не менее получается, что при сбросе микроконтроллера бит HSEON не обнуляется. Что это? Ошибка в документации? Возможно, в комментариях кто-то разъяснит этот момент… А может мы и сами в этом разберемся, когда дойдем до темы про тактование. Сейчас не будем забивать себе голову. Работает — отлично, плывем дальше, к другому подводному камню.

Настройка PA13 и PA14, как портов ввода-вывода
Строго говоря, это не совсем подводный камень — при беглом чтении документации все становится понятно:

The debug pins are in AF pull-up/pull-down after reset:

  • PA14: SWCLK in pull-down
  • PA13: SWDIO in pull-up 

То есть, после сброса пины отладки (PA13, 14) находятся в режиме альтернативной функции (AF, настраивается в регистре MODER), причем PA13 настроен с подтяжкой в питалову, и PA14 — к земле (настраивается в регистре PUPDR). В таблице регистров можно в этом убедиться (для MODER уже рассмотрено выше, для PUPDR посмотрите сами). Таким образом, достаточно держать руки регистры в чистоте для корректной работы:

 #include "stm32f0xx.h"  void delay (int a);  void main(void) {   RCC->AHBENR = RCC_AHBENR_GPIOAEN;  GPIOA->MODER &= ~(GPIO_MODER_MODER13  GPIO_MODER_MODER14);  GPIOA->MODER = (GPIO_MODER_MODER13_0  GPIO_MODER_MODER14_0);  GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_13  GPIO_OTYPER_OT_14);  GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR13  GPIO_PUPDR_PUPDR14);  while (1) {   GPIOA->BSRR = GPIO_BSRR_BS_13;   GPIOA->BSRR = GPIO_BSRR_BR_14;   delay(500000);   GPIOA->BSRR = GPIO_BSRR_BS_14;   GPIOA->BSRR = GPIO_BSRR_BR_13;   delay(500000);  } }  void delay (int a) {  int i,j;  for (i=0; i < a; i++) {   j++;  } } 

Компилируем, заливаем, работает!

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

Буль-буль… библиотека
Строго говоря, это никакая не библиотека. Скорее, заголовочный файл с макросами. Позволяет облегчить работу с GPIO, не прибегая к богомерзкому SPL (ну давайте, HALявщики, расскажите в комментариях, что писать на CMSIS — извращение!). Создадим заголовочный файл def.h — там будут находиться наши макросы. Заодно, определим там же константы LED1 и LED2 для двух светодиодов:

 #define LED1          3 #define LED2          4  #define TEMPF(ARG1, ARG2, ARG3)         ARG1##ARG2##ARG3   #define MODER_11(MOD_PORT)       TEMPF(GPIO_MODER_MODER, MOD_PORT,) #define MODER_01(MOD_PORT)       TEMPF(GPIO_MODER_MODER, MOD_PORT,_0) #define MODER_10(MOD_PORT)       TEMPF(GPIO_MODER_MODER, MOD_PORT,_1)  #define OTYPER(OTYPER_PORT)      TEMPF(GPIO_OTYPER_OT, _ , OTYPER_PORT) #define IDR(IDR_PORT)            TEMPF(GPIO_IDR, _ , IDR_PORT) #define BS(BS_PORT)            TEMPF(GPIO_BSRR_BS, _ , BS_PORT) #define BR(BR_PORT)            TEMPF(GPIO_BSRR_BR, _ , BR_PORT)  #define PUPDR_11(PUPDR_PORT)     TEMPF(GPIO_PUPDR_PUPDR, PUPDR_PORT,) #define PUPDR_01(PUPDR_PORT)     TEMPF(GPIO_PUPDR_PUPDR, PUPDR_PORT,_0) #define PUPDR_10(PUPDR_PORT)     TEMPF(GPIO_PUPDR_PUPDR, PUPDR_PORT,_1)  #define OSPEEDR_11(SPEED_PORT)       TEMPF(GPIO_OSPEEDER_OSPEEDR, SPEED_PORT,) #define OSPEEDR_01(SPEED_PORT)       TEMPF(GPIO_OSPEEDER_OSPEEDR, SPEED_PORT,_0) #define OSPEEDR_10(SPEED_PORT)       TEMPF(GPIO_OSPEEDER_OSPEEDR, SPEED_PORT,_1) 

Использовать очень просто. Обнулить регистр MODER для LED1 и LED2:

 GPIOA->MODER &= ~(MODER_11(LED1)  MODER_11(LED2));

строить MODER в режиме 01? Ничего проще:

 GPIOA->MODER = MODER_01(LED1)  MODER_01(LED2) ;

Зажечь лампочку, погасить лампочку:

 GPIOA->BSRR = BS(LED1); GPIOA->BSRR = BR(LED2);

Получилось весьма наглядно:

 #include "stm32f0xx.h" #include "def.h"   void delay (int a);   void main(void) {  RCC->AHBENR = RCC_AHBENR_GPIOAEN;  GPIOA->MODER &= ~(MODER_11(LED1)  MODER_11(LED2));  GPIOA->MODER = MODER_01(LED1)  MODER_01(LED2) ;  GPIOA->OTYPER &= ~(OTYPER(LED1)  OTYPER(LED2)) ;  while (1) {   GPIOA->BSRR = BS(LED1);   GPIOA->BSRR = BR(LED2);   delay(500000);   GPIOA->BSRR = BS(LED2);   GPIOA->BSRR = BR(LED1);   delay(500000);  } }   void delay (int a) {  int i,j;  for (i=0; i < a; i++) {   j++;  } }

Не забываем добавить файлик def.h в папку inc и в сам проект:

Компилируем, проверяем, должно все работать! А теперь съешьте ещё этих мягких французских булок, да выпейте чаю. Впереди у нас таймеры: а там и прерывания, и альтернативные функции и даже (может быть) подключение энкодера…