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:

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

RCC->

Должен появиться выпадающий список, как на картинке. Если его не появилось, попробуйте перезапустить IAR и открыть проект заново (также проверьте, что у вас подключен заголовочный файл stm32f0xx.h). Не нужно быть семи пядей во лбу, чтобы понять, что для подключения к шине AHB нужно выбрать AHBENR [AHB Enable Register]. Дальше комбинация «или-равно» ( |= ), а что писать после знака равенства? Памятуя о том, что «магические» числа — зло, будем использовать стандартные константы. Они объявлены в файле 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 для 2-х светодиодов:

#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 и в сам проект:

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

Прикрепленные файлы:

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

Ваш адрес email не будет опубликован.